A function pointer stores the address of a function. Unlike data pointers, function pointers point to executable code, not data. They enable callbacks, dynamic dispatch, and strategy patterns — fundamental building blocks of flexible C programs.
function_pointer_basics.c
C
1
#include <stdio.h>
2
3
/* Regular functions */
4
int add(int a, int b) { return a + b; }
5
int subtract(int a, int b) { return a - b; }
6
int multiply(int a, int b) { return a * b; }
7
8
int main(void) {
9
/* Declare a function pointer: return_type (*name)(param_types) */
10
int (*operation)(int, int);
11
12
/* Assign the address of a function (no & needed, but allowed) */
Function pointer syntax is verbose and error-prone. Using typedef creates a type alias that makes declarations readable and reduces mistakes. This is especially valuable for callback-heavy code.
typedef_fnptr.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
/* Define type aliases for function pointer signatures */
6
typedef int (*compare_fn)(const void*, const void*);
7
typedef void (*destroy_fn)(void*);
8
typedef void (*print_fn)(const void*);
9
typedef double (*transform_fn)(double);
10
11
/* A struct that holds function pointers — a "vtable" / strategy pattern */
12
typedef struct {
13
compare_fn compare;
14
destroy_fn destroy;
15
print_fn print;
16
const char* name;
17
} CollectionOps;
18
19
/* Functions that accept the type alias */
20
void sort_array(void* arr, size_t n, size_t elem_size, compare_fn cmp) {
21
qsort(arr, n, elem_size, cmp);
22
}
23
24
void transform_array(double* arr, size_t n, transform_fn fn) {
25
for (size_t i = 0; i < n; i++) {
26
arr[i] = fn(arr[i]);
27
}
28
}
29
30
/* Concrete implementations for the ops struct */
31
static int int_compare(const void* a, const void* b) {
32
return (*(const int*)a - *(const int*)b);
33
}
34
35
static void int_print(const void* val) {
36
printf("%d", *(const int*)val);
37
}
38
39
static void int_destroy(void* val) {
40
free(val);
41
}
42
43
int main(void) {
44
CollectionOps int_ops = {
45
.compare = int_compare,
46
.destroy = int_destroy,
47
.print = int_print,
48
.name = "integer"
49
};
50
51
printf("Collection type: %s\n", int_ops.name);
52
53
int nums[] = {5, 2, 8, 1, 9, 3};
54
size_t n = sizeof(nums) / sizeof(nums[0]);
55
56
sort_array(nums, n, sizeof(int), int_ops.compare);
57
58
printf("Sorted: ");
59
for (size_t i = 0; i < n; i++) {
60
int_ops.print(&nums[i]);
61
if (i < n - 1) printf(", ");
62
}
63
printf("\n");
64
65
return 0;
66
}
Callback Example: qsort
The standard library qsort function takes a function pointer as its last argument. This is the canonical example of a callback — the sort algorithm calls your comparison function to determine element ordering.
qsort_callback.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
typedef struct {
6
char name[32];
7
int score;
8
} Student;
9
10
/* qsort callback: must return <0, 0, or >0 */
11
static int compare_by_score(const void* a, const void* b) {
12
const Student* sa = (const Student*)a;
13
const Student* sb = (const Student*)b;
14
/* Descending order by score */
15
return sb->score - sa->score;
16
}
17
18
static int compare_by_name(const void* a, const void* b) {
19
const Student* sa = (const Student*)a;
20
const Student* sb = (const Student*)b;
21
return strcmp(sa->name, sb->name);
22
}
23
24
static void print_student(const Student* s) {
25
printf(" %-15s %3d\n", s->name, s->score);
26
}
27
28
int main(void) {
29
Student students[] = {
30
{"Alice", 92},
31
{"Bob", 85},
32
{"Charlie", 98},
33
{"Diana", 77},
34
{"Eve", 95},
35
};
36
size_t n = sizeof(students) / sizeof(students[0]);
37
38
/* Sort by score (descending) */
39
qsort(students, n, sizeof(Student), compare_by_score);
40
printf("By score:\n");
41
for (size_t i = 0; i < n; i++) print_student(&students[i]);
42
43
printf("\n");
44
45
/* Sort by name (alphabetical) */
46
qsort(students, n, sizeof(Student), compare_by_name);
47
printf("By name:\n");
48
for (size_t i = 0; i < n; i++) print_student(&students[i]);
49
50
return 0;
51
}
Array of Function Pointers: Dispatch Tables
An array of function pointers creates a dispatch table (jump table) — a data-driven way to select behavior. This replaces long switch statements with array lookups, improving extensibility and readability.
dispatch_table.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
/* Calculator operations */
5
static double op_add(double a, double b) { return a + b; }
6
static double op_sub(double a, double b) { return a - b; }
7
static double op_mul(double a, double b) { return a * b; }
8
static double op_div(double a, double b) { return b != 0.0 ? a / b : 0.0; }
9
static double op_mod(double a, double b) { return b != 0.0 ? (int)a % (int)b : 0.0; }
void* is a generic pointer that can point to any data type. It is the C mechanism for polymorphism — malloc returns void*, and generic data structures use it to store elements of any type. You must cast void* to the correct type before dereferencing.
generic_void_ptr.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
/* A generic swap that works with any data type */
6
void generic_swap(void* a, void* b, size_t elem_size) {
A generic linked list stores void* in each node, allowing it to hold any data type. The caller provides functions for printing, comparing, and freeing elements. This is how C achieves type-safe generic containers.
generic_linked_list.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
4
typedef struct Node {
5
void* data;
6
struct Node* next;
7
} Node;
8
9
typedef struct {
10
Node* head;
11
size_t size;
12
size_t elem_size;
13
void (*free_fn)(void*);
14
void (*print_fn)(const void*);
15
int (*cmp_fn)(const void*, const void*);
16
} LinkedList;
17
18
LinkedList* list_create(size_t elem_size,
19
void (*free_fn)(void*),
20
void (*print_fn)(const void*),
21
int (*cmp_fn)(const void*, const void*)) {
22
LinkedList* list = malloc(sizeof(LinkedList));
23
if (!list) return NULL;
24
list->head = NULL;
25
list->size = 0;
26
list->elem_size = elem_size;
27
list->free_fn = free_fn;
28
list->print_fn = print_fn;
29
list->cmp_fn = cmp_fn;
30
return list;
31
}
32
33
int list_push_front(LinkedList* list, const void* data) {
if (found) printf("Found: %d\n", *found); /* Found: 30 */
97
98
list_destroy(list);
99
return 0;
100
}
Pointer to Pointer (int **pp)
A pointer to a pointer holds the address of a pointer variable. This is essential for: (1) modifying a pointer inside a function (e.g., malloc via double pointer), (2) dynamic 2D arrays, and (3) arrays of strings.
pointer_to_pointer.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
/* Without pointer-to-pointer: cannot modify the original pointer */
6
void bad_alloc(int* p) {
7
p = malloc(sizeof(int)); /* Only modifies the local copy */
8
*p = 42;
9
/* Original pointer in caller is unchanged! */
10
}
11
12
/* With pointer-to-pointer: can modify the caller's pointer */
13
void good_alloc(int** pp) {
14
*pp = malloc(sizeof(int)); /* Modifies the pointer at the address */
15
**pp = 42;
16
/* Caller's pointer now points to the allocated memory */
17
}
18
19
/* Reallocate a buffer (like realloc but with error handling) */
These two declarations look similar but are fundamentally different: int *arr[10] is an array of 10 pointers to int. int (*arr)[10] is a pointer to an array of 10 ints. Understanding this distinction is critical for working with multi-dimensional arrays.
Complex C declarations can be read using the "right-left rule" (clockwise spiral rule): start at the variable name, move right when you can, then left, alternating. This helps decode even the most confusing declarations.
complex_declarations.c
C
1
#include <stdio.h>
2
3
/* Reading declarations with the right-left rule */
4
5
/* Step 1: int *p */
6
/* p is a ... pointer to ... int */
7
int *p;
8
9
/* Step 2: int **pp */
10
/* pp is a ... pointer to ... pointer to ... int */
11
int **pp;
12
13
/* Step 3: int (*fp)(int, int) */
14
/* fp is a ... pointer to ... function (int, int) returning int */
15
int (*fp)(int, int);
16
17
/* Step 4: int *arr[10] */
18
/* arr is a ... array[10] of ... pointer to ... int */
19
int *arr[10];
20
21
/* Step 5: int (*arr)[10] */
22
/* arr is a ... pointer to ... array[10] of int */
23
int (*arr)[10];
24
25
/* Step 6: char *(*callback)(void*) */
26
/* callback is a ... pointer to ... function (void*) returning pointer to char */
/* signal is a ... function (int, pointer to function(int) returning void) */
31
/* returning ... pointer to ... function (int) returning void */
32
/* This is the actual signal() signature from <signal.h> */
33
34
/* Step 8: int (*(*fp)(int))[10] */
35
/* fp is a ... pointer to ... function (int) returning ... */
36
/* pointer to ... array[10] of int */
37
38
int main(void) {
39
printf("Declarations compile — the types are correct\n");
40
return 0;
41
}
ℹ
info
When a declaration is too complex, use typedef to break it into named parts. This makes code self-documenting and avoids reading complex declarations repeatedly.
const and Pointers
The placement of const relative to the * determines what is immutable: the data being pointed to, the pointer itself, or both. This is a frequent source of confusion but is essential for writing safe, expressive code.
const_pointers.c
C
1
#include <stdio.h>
2
#include <string.h>
3
4
int main(void) {
5
int x = 10;
6
int y = 20;
7
8
/* const int *p — pointer to const data (data is read-only through p) */
9
const int* p = &x;
10
/* *p = 30; ERROR: cannot modify data through p */
11
p = &y; /* OK: can change what p points to */
12
printf("*p = %d\n", *p); /* 20 */
13
14
/* int * const p — const pointer (pointer cannot be reassigned) */
15
int* const q = &x;
16
*q = 30; /* OK: can modify data through q */
17
/* q = &y; ERROR: cannot change what q points to */
/* Practical use: function that reads but doesn't modify */
30
/* void print(const char* s) — promises not to modify s */
31
32
/* const with array decay */
33
const char str[] = "immutable";
34
/* str[0] = 'X'; ERROR: array elements are const */
35
36
return 0;
37
}
Declaration
Data
Pointer
Can Modify
int *p
Mutable
Mutable
Both data and pointer
const int *p
Read-only
Mutable
Pointer only
int * const p
Mutable
Read-only
Data only
const int * const p
Read-only
Read-only
Neither
The restrict Keyword (C99)
restrictis a promise to the compiler that a pointer is the only way to access the data it points to during the function's lifetime. This allows the compiler to generate more aggressive optimizations by assuming no pointer aliasing.
restrict_keyword.c
C
1
#include <stdio.h>
2
#include <string.h>
3
4
/* Without restrict: compiler must assume overlapping memory */
/* This is undefined behavior with memcpy (overlap) */
26
/* memcpy(a + 1, a, 4 * sizeof(int)); */
27
28
/* This is safe with memmove (handles overlap) */
29
memmove(a + 1, a, 4 * sizeof(int));
30
for (int i = 0; i < 5; i++) printf("%d ", a[i]);
31
printf("\n"); /* 1 1 2 3 4 */
32
33
/* Restrict only matters for pointer parameters */
34
/* It is a hint — violating it is undefined behavior */
35
/* Most modern compilers support it (GCC, Clang) */
36
37
return 0;
38
}
Opaque Pointers: Hiding Implementation
Opaque pointers hide the internal structure of a type behind a void* or forward-declared incomplete type. The header declares functions that operate on the type but does not expose its fields. This achieves data hiding and encapsulation in C.
include/stack.h
C
1
/* stack.h — public API (opaque pointer pattern) */
2
#ifndef STACK_H
3
#define STACK_H
4
5
#include <stddef.h>
6
7
/* Forward declaration — users know it exists but not its layout */
8
typedef struct Stack Stack;
9
10
Stack* stack_create(size_t elem_size);
11
void stack_destroy(Stack* s);
12
int stack_push(Stack* s, const void* data);
13
void* stack_peek(const Stack* s);
14
int stack_pop(Stack* s, void* out);
15
size_t stack_size(const Stack* s);
16
17
#endif /* STACK_H */
src/stack.c
C
1
/* stack.c — private implementation */
2
#include "stack.h"
3
#include <stdlib.h>
4
#include <string.h>
5
6
/* Users cannot see this structure — it is defined only here */
7
struct Stack {
8
void* data; /* Dynamic array of elements */
9
size_t elem_size; /* Size of each element in bytes */
10
size_t capacity; /* Maximum elements before realloc */
Opaque pointers prevent users from depending on internal layout, so you can change the implementation (switch from array to linked list, change data structures) without breaking any code that uses the API.
State Machine with Function Pointer Tables
Function pointer tables enable elegant state machine implementations. Each state has an associated action function, and transitions are driven by a table lookup rather than nested switch statements.
state_machine.c
C
1
#include <stdio.h>
2
#include <string.h>
3
4
typedef enum {
5
STATE_IDLE,
6
STATE_RUNNING,
7
STATE_PAUSED,
8
STATE_ERROR,
9
STATE_COUNT
10
} State;
11
12
typedef enum {
13
EVT_START,
14
EVT_PAUSE,
15
EVT_RESUME,
16
EVT_STOP,
17
EVT_ERROR,
18
EVT_COUNT
19
} Event;
20
21
typedef State (*StateHandler)(void* context);
22
23
/* State transition table */
24
static State transition_table[STATE_COUNT][EVT_COUNT];
Returning pointers from functions has strict rules. Never return a pointer to a local variable (it becomes dangling). You can return pointers to: heap-allocated memory, static variables, or string literals. Each has different ownership implications.
returning_pointers.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
/* BAD: Returns pointer to local variable (dangling pointer) */
6
int* bad_return(void) {
7
int local = 42;
8
return &local; /* Undefined behavior — local is on the stack */
9
}
10
11
/* GOOD: Return heap-allocated memory (caller must free) */
12
int* good_return_heap(int value) {
13
int* p = malloc(sizeof(int));
14
if (p) *p = value;
15
return p; /* Caller is responsible for free() */
16
}
17
18
/* GOOD: Return pointer to static variable (persists between calls) */
19
int* good_return_static(int value) {
20
static int saved;
21
saved = value;
22
return &saved; /* Valid until next call overwrites it */
23
}
24
25
/* GOOD: Return string literal (lives for entire program duration) */
26
const char* good_return_literal(int code) {
27
switch (code) {
28
case 200: return "OK";
29
case 404: return "Not Found";
30
case 500: return "Server Error";
31
default: return "Unknown";
32
}
33
}
34
35
/* GOOD: Return dynamically built string (caller must free) */
36
char* build_string(const char* a, const char* b) {
37
size_t len = strlen(a) + strlen(b) + 1;
38
char* result = malloc(len);
39
if (result) {
40
strcpy(result, a);
41
strcat(result, b);
42
}
43
return result;
44
}
45
46
int main(void) {
47
/* Heap allocation — caller frees */
48
int* hp = good_return_heap(100);
49
printf("Heap: %d\n", *hp);
50
free(hp);
51
52
/* Static variable — valid but overwritten on next call */
53
int* sp1 = good_return_static(10);
54
printf("Static 1: %d\n", *sp1);
55
int* sp2 = good_return_static(20);
56
printf("Static 2: %d\n", *sp2);
57
/* Note: *sp1 and *sp2 point to the same memory! */
58
59
/* String literals — no need to free */
60
printf("Status: %s\n", good_return_literal(200));
61
62
/* Dynamic string — caller frees */
63
char* str = build_string("Hello, ", "World!");
64
printf("Built: %s\n", str);
65
free(str);
66
67
return 0;
68
}
Signal Handling with Function Pointers
Signal handlers are callbacks invoked by the operating system when a signal (event) occurs. signal() and sigaction() register function pointers that handle asynchronous events like interrupts, termination requests, and errors.
signal_handler.c
C
1
#include <stdio.h>
2
#include <signal.h>
3
#include <unistd.h>
4
#include <stdatomic.h>
5
6
/* Signal flag — must be volatile sig_atomic_t or atomic */
7
static volatile sig_atomic_t got_signal = 0;
8
9
void handle_sigint(int signum) {
10
/* Only async-signal-safe functions allowed here! */
printf("Received signal %d, shutting down gracefully.\n", got_signal);
34
} else {
35
printf("Work completed normally.\n");
36
}
37
38
return 0;
39
}
⚠
warning
Signal handlers run asynchronously. Only use async-signal-safe functions (write, not printf; no malloc, no locks). Keep handlers minimal — set a flag and handle it in the main loop.
Complete Example: Generic Array with Callbacks
A generic dynamic array that stores void* elements with function pointer callbacks for operations. Demonstrates combining function pointers, void*, and pointer-to-pointer in a practical data structure.
generic_array_complete.c
C
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
5
typedef void (*GArrayFreeFn)(void*);
6
typedef void (*GArrayPrintFn)(const void*);
7
typedef int (*GArrayCmpFn)(const void*, const void*);