Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Advanced C Pointers: Function Pointers, Callbacks, and Custom Sorting

Tech May 12 4

1. Function Pointers

1.1 Locating the Function Address

Functions occupy memory space and thus have addresses. The function name itself repersents the starting address of the function code, and using the address-of operator & on the name yields the same result.

#include <stdio.h>

float multiply(float x, float y) {
    return x * y;
}

int main() {
    printf("Address via name: %p\n", multiply);
    printf("Address via &: %p\n", &multiply);
    return 0;
}

1.2 Declaring Function Pointer Variables

To store a function's address, you use a function pointer. The syntax requires wrapping the pointer name with * and parentheses, followed by the parameter types.

Syntax: return_type (*pointer_name)(parameter_types);

// Storing the address of the multiply function
float (*math_op)(float, float) = multiply; 
// Note: &multiply and multiply are interchangeable here

1.3 Invoking Functions via Pointers

You can execute the function using the pointer either by dereferencing it or using the pointer name directly.

float result_a = (*math_op)(2.5, 4.0); // Dereferencing style
float result_b = math_op(2.5, 4.0);    // Direct call style

Unlike standard data pointers, function pointers do not support arithmetic operations like incrementing or decrementing.

2. Decoding Complex Declarations

2.1 Simplifying with typedef

The typedef keyword creates aliases for complex types, making code more readable.

// Simplifying a long integer type
typedef long double decimal;
decimal price = 19.99;

2.2 Analysis of Complex Expresssions

Example A: (*(void (*)())0)();

  1. The inner void (*)() defines a function pointer type taking no arguments and returning void.
  2. (void (*)())0 casts the memory address 0 into that function pointer type.
  3. *(...) dereferences the pointer to get the function.
  4. (...) at the end invokes the function.

Cleaned up with typedef:

typedef void (*func_ptr)();
(*(func_ptr)0)();

Example B: void (*signal(int, void(*)(int)))(int);

  1. signal is a function taking an int and a function pointer void(*)(int).
  2. The return type of signal is a function pointer: void (*)(int).

Cleaned up with typedef:

typedef void (*handler_t)(int);
handler_t signal(int sig, handler_t h);

3. Building a Calculator with Pointers

Instead of a massive switch-case block, we can use function pointers to handle operations dynamically.

#include <stdio.h>

double sum(double a, double b) { return a + b; }
double difference(double a, double b) { return a - b; }
double product(double a, double b) { return a * b; }
double quotient(double a, double b) { return a / b; }

3.1 Using Arrays of Function Pointers

An array where every element is a function pointer allows indexed acccess to functions.

int main() {
    // Array of function pointers (index 0 is NULL for 1-based UI)
    double (*ops[5])(double, double) = {NULL, sum, difference, product, quotient};
    
    int choice;
    double num1, num2;
    
    do {
        printf("1:Add 2:Sub 3:Mul 4:Div 0:Exit\nChoice: ");
        scanf("%d", &choice);
        
        if (choice >= 1 && choice <= 4) {
            printf("Enter two numbers: ");
            scanf("%lf %lf", &num1, &num2);
            printf("Result: %f\n", ops[choice](num1, num2));
        }
    } while (choice != 0);
    return 0;
}

3.2 Implementing Callbacks

A callback occurs when a function receives a pointer to another function and executes it.

void calculate(double (*operation)(double, double)) {
    double x, y;
    printf("Enter operands: ");
    scanf("%lf %lf", &x, &y);
    printf("Output: %f\n", operation(x, y));
}

int main() {
    int cmd;
    do {
        printf("1:Add 2:Sub 3:Mul 4:Div 0:Exit\nChoice: ");
        scanf("%d", &cmd);
        
        switch(cmd) {
            case 1: calculate(sum); break;
            case 2: calculate(difference); break;
            case 3: calculate(product); break;
            case 4: calculate(quotient); break;
        }
    } while (cmd != 0);
    return 0;
}

4. The qsort Function and Custom Implementation

4.1 Using Standard qsort

The standard library qsort sorts arrays using a comparison callback.

Prototype: void qsort(void *base, size_t num, size_t size, int (*compar)(const void *, const void *));

#include <stdio.h>
#include <stdlib.h>

int compare_ints(const void *a, const void *b) {
    int int_a = *(int*)a;
    int int_b = *(int*)b;
    return (int_a > int_b) - (int_a < int_b); // Safe comparison
}

int main() {
    int data[] = {9, 3, 1, 7, 5};
    int len = sizeof(data) / sizeof(data[0]);
    
    qsort(data, len, sizeof(int), compare_ints);
    
    for(int i=0; i<len; i++) printf("%d ", data[i]);
    return 0;
}

4.2 Bubble Sort Logic

A simple sorting algorithm that swaps adjacent elements if they are in the wrong order.

void bubble_sort_ints(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {
        for (int j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

4.3 Simulating qsort with Bubble Sort

We can write a generic sorting function that mimics qsort by using void* pointers and byte-level swapping.

void swap_bytes(void *elem1, void *elem2, size_t width) {
    char *p1 = (char*)elem1;
    char *p2 = (char*)elem2;
    for (size_t i = 0; i < width; i++) {
        char tmp = p1[i];
        p1[i] = p2[i];
        p2[i] = tmp;
    }
}

void generic_bubble(void *base, size_t total, size_t size, 
                    int (*cmp)(const void*, const void*)) {
    char *start = (char*)base;
    for (size_t i = 0; i < total - 1; i++) {
        for (size_t j = 0; j < total - i - 1; j++) {
            // Calculate addresses of adjacent elements
            void *curr = start + (j * size);
            void *next = start + ((j + 1) * size);
            
            if (cmp(curr, next) > 0) {
                swap_bytes(curr, next, size);
            }
        }
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.