Advanced C Pointers: Function Pointers, Callbacks, and Custom Sorting
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)();
- The inner
void (*)()defines a function pointer type taking no arguments and returning void. (void (*)())0casts the memory address0into that function pointer type.*(...)dereferences the pointer to get the function.(...)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);
signalis a function taking anintand a function pointervoid(*)(int).- The return type of
signalis 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);
}
}
}
}