Docs
Pointers and Functions
Pointers and Functions
Table of Contents
- •Introduction
- •Passing Pointers to Functions
- •Call by Value vs Call by Reference
- •Returning Pointers from Functions
- •Pointer to Functions
- •Array of Function Pointers
- •Callback Functions
- •Functions Returning Multiple Values
- •Const Pointers in Functions
- •Generic Functions with void Pointers
- •Common Mistakes and Pitfalls
- •Best Practices
- •Summary
Introduction
Pointers and functions work together to provide powerful capabilities in C:
- •Modify variables in the caller's scope (pass by reference)
- •Return multiple values from a function
- •Pass large data efficiently without copying
- •Create callback mechanisms for flexible designs
- •Implement function tables for dynamic dispatch
Understanding how pointers interact with functions is essential for effective C programming.
Passing Pointers to Functions
Basic Syntax
void function_name(datatype *pointer_parameter) {
// Function can access and modify data through pointer
}
// Calling:
int variable;
function_name(&variable); // Pass address
Example: Modifying a Value
#include <stdio.h>
void doubleValue(int *ptr) {
*ptr = *ptr * 2; // Modify the original
}
int main() {
int num = 10;
printf("Before: %d\n", num); // 10
doubleValue(&num); // Pass address
printf("After: %d\n", num); // 20
return 0;
}
How It Works
main():
num = 10
&num = 0x1000
doubleValue(int *ptr):
ptr = 0x1000 (copy of address)
*ptr = 10 (original value)
*ptr = *ptr * 2 → 20
After return:
num = 20 (modified!)
Call by Value vs Call by Reference
Call by Value (Default in C)
void byValue(int x) {
x = 100; // Only modifies local copy
}
int main() {
int a = 10;
byValue(a);
printf("%d\n", a); // Still 10
return 0;
}
Memory:
main: a = 10 at address 0x1000
function: x = 10 at address 0x2000 (different copy)
Call by Reference (Using Pointers)
void byReference(int *x) {
*x = 100; // Modifies original
}
int main() {
int a = 10;
byReference(&a);
printf("%d\n", a); // Now 100
return 0;
}
Memory:
main: a = 10 at address 0x1000
function: x = 0x1000 (points to a)
*x modifies 0x1000 directly
Comparison Table
| Aspect | Call by Value | Call by Reference |
|---|---|---|
| What's passed | Copy of value | Address of variable |
| Original modified | No | Yes |
| Memory overhead | Copy created | Only pointer (8 bytes) |
| Syntax | func(var) | func(&var) |
| Parameter type | type var | type *var |
Returning Pointers from Functions
Safe: Returning Pointer to Static/Global Data
int* getStaticValue(void) {
static int value = 100; // Static - persists
return &value;
}
int main() {
int *ptr = getStaticValue();
printf("%d\n", *ptr); // 100
return 0;
}
Safe: Returning Dynamically Allocated Memory
int* createArray(int size) {
int *arr = (int*)malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
return arr; // Caller must free!
}
int main() {
int *arr = createArray(5);
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
free(arr); // Don't forget!
return 0;
}
DANGER: Returning Pointer to Local Variable
int* dangerous(void) {
int local = 100; // Local variable
return &local; // WRONG! Returns dangling pointer
} // local is destroyed here
int main() {
int *ptr = dangerous();
printf("%d\n", *ptr); // Undefined behavior!
return 0;
}
Warning: Never return address of local variables!
Valid Return Pointer Scenarios
| Source | Safe? | Notes |
|---|---|---|
| Static variable | ✅ Yes | Persists after function returns |
| Global variable | ✅ Yes | Persists for program lifetime |
| malloc/calloc | ✅ Yes | Caller must free |
| Parameter pointer | ✅ Yes | Points to caller's data |
| Local variable | ❌ No | Destroyed after return |
| Local array | ❌ No | Destroyed after return |
Pointer to Functions
What is a Function Pointer?
A function pointer stores the address of a function, allowing:
- •Functions to be passed as arguments
- •Dynamic function selection at runtime
- •Callback implementations
Declaration Syntax
return_type (*pointer_name)(parameter_types);
Examples
// Pointer to function taking int, returning int
int (*funcPtr)(int);
// Pointer to function taking two ints, returning int
int (*operation)(int, int);
// Pointer to function taking no args, returning void
void (*callback)(void);
Basic Usage
#include <stdio.h>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int main() {
// Declare function pointer
int (*operation)(int, int);
// Point to add function
operation = add; // or: operation = &add;
printf("5 + 3 = %d\n", operation(5, 3)); // 8
// Point to subtract function
operation = subtract;
printf("5 - 3 = %d\n", operation(5, 3)); // 2
return 0;
}
Calling Function Pointers
int (*fp)(int, int) = add;
// Both are valid ways to call:
int result1 = fp(5, 3); // Direct call
int result2 = (*fp)(5, 3); // Explicit dereference
Using typedef for Clarity
// Define a type for the function pointer
typedef int (*MathOperation)(int, int);
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int main() {
MathOperation op;
op = add;
printf("Sum: %d\n", op(10, 5)); // 15
op = multiply;
printf("Product: %d\n", op(10, 5)); // 50
return 0;
}
Array of Function Pointers
Concept
Create an array where each element is a function pointer, enabling:
- •Menu-driven programs
- •State machines
- •Command dispatchers
Example: Calculator
#include <stdio.h>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
int main() {
// Array of function pointers
int (*operations[4])(int, int) = {add, subtract, multiply, divide};
char *names[] = {"Add", "Subtract", "Multiply", "Divide"};
int a = 20, b = 5;
for (int i = 0; i < 4; i++) {
printf("%s: %d\n", names[i], operations[i](a, b));
}
return 0;
}
Output:
Add: 25
Subtract: 15
Multiply: 100
Divide: 4
Using typedef
typedef int (*Operation)(int, int);
Operation ops[] = {add, subtract, multiply, divide};
Callback Functions
What is a Callback?
A callback is a function passed as an argument to another function, to be called later.
Basic Callback Example
#include <stdio.h>
// Callback type
typedef void (*Callback)(int);
// Function that uses callback
void processArray(int arr[], int size, Callback cb) {
for (int i = 0; i < size; i++) {
cb(arr[i]); // Call the callback for each element
}
}
// Callback implementations
void printElement(int x) {
printf("%d ", x);
}
void printSquare(int x) {
printf("%d ", x * x);
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int size = 5;
printf("Elements: ");
processArray(arr, size, printElement);
printf("\n");
printf("Squares: ");
processArray(arr, size, printSquare);
printf("\n");
return 0;
}
Output:
Elements: 1 2 3 4 5
Squares: 1 4 9 16 25
Sorting with Callback (qsort style)
#include <stdio.h>
#include <stdlib.h>
// Comparison function type
typedef int (*CompareFunc)(const void*, const void*);
// Simple bubble sort with callback
void mySort(int arr[], int size, CompareFunc compare) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1 - i; j++) {
if (compare(&arr[j], &arr[j+1]) > 0) {
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
// Ascending order
int compareAsc(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}
// Descending order
int compareDesc(const void *a, const void *b) {
return *(int*)b - *(int*)a;
}
int main() {
int arr1[] = {5, 2, 8, 1, 9};
int arr2[] = {5, 2, 8, 1, 9};
int size = 5;
mySort(arr1, size, compareAsc);
printf("Ascending: ");
for (int i = 0; i < size; i++) printf("%d ", arr1[i]);
mySort(arr2, size, compareDesc);
printf("\nDescending: ");
for (int i = 0; i < size; i++) printf("%d ", arr2[i]);
return 0;
}
Functions Returning Multiple Values
Using Pointer Parameters
#include <stdio.h>
void getMinMax(int arr[], int size, int *min, int *max) {
*min = *max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
int main() {
int arr[] = {3, 7, 1, 9, 4, 6};
int min, max;
getMinMax(arr, 6, &min, &max);
printf("Min: %d, Max: %d\n", min, max); // Min: 1, Max: 9
return 0;
}
Returning Struct (Alternative)
typedef struct {
int min;
int max;
} MinMax;
MinMax getMinMax(int arr[], int size) {
MinMax result = {arr[0], arr[0]};
for (int i = 1; i < size; i++) {
if (arr[i] < result.min) result.min = arr[i];
if (arr[i] > result.max) result.max = arr[i];
}
return result;
}
Division with Quotient and Remainder
void divide(int dividend, int divisor, int *quotient, int *remainder) {
*quotient = dividend / divisor;
*remainder = dividend % divisor;
}
int main() {
int q, r;
divide(17, 5, &q, &r);
printf("17 / 5 = %d remainder %d\n", q, r); // 3 remainder 2
return 0;
}
Const Pointers in Functions
Protecting Input Data
// Promise not to modify the array
int sum(const int *arr, int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
// arr[i] = 0; // Compiler error! Can't modify
}
return total;
}
Const Patterns
// Pointer to constant data (can't modify data)
void func1(const int *p);
// Constant pointer (can't change what it points to)
void func2(int * const p);
// Both constant
void func3(const int * const p);
Best Practice for Read-Only Parameters
// Good: clearly indicates arr is input-only
void printArray(const int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// Also good for strings
int strlen_custom(const char *str) {
int len = 0;
while (*str++) len++;
return len;
}
Generic Functions with void Pointers
The void* Type
void* is a generic pointer that can point to any type:
void* ptr;
int x = 10;
float f = 3.14;
ptr = &x; // OK
ptr = &f; // Also OK
Generic Swap Function
#include <stdio.h>
#include <string.h>
void genericSwap(void *a, void *b, size_t size) {
char temp[size]; // VLA for temp storage
memcpy(temp, a, size);
memcpy(a, b, size);
memcpy(b, temp, size);
}
int main() {
// Swap integers
int x = 10, y = 20;
printf("Before: x=%d, y=%d\n", x, y);
genericSwap(&x, &y, sizeof(int));
printf("After: x=%d, y=%d\n", x, y);
// Swap doubles
double a = 1.5, b = 2.5;
printf("Before: a=%.1f, b=%.1f\n", a, b);
genericSwap(&a, &b, sizeof(double));
printf("After: a=%.1f, b=%.1f\n", a, b);
return 0;
}
Generic Print Function
typedef enum { TYPE_INT, TYPE_FLOAT, TYPE_CHAR } DataType;
void genericPrint(void *data, DataType type) {
switch (type) {
case TYPE_INT:
printf("%d", *(int*)data);
break;
case TYPE_FLOAT:
printf("%.2f", *(float*)data);
break;
case TYPE_CHAR:
printf("%c", *(char*)data);
break;
}
}
Common Mistakes and Pitfalls
1. Returning Address of Local Variable
// WRONG!
int* badFunction(void) {
int x = 10;
return &x; // x is destroyed after return
}
// CORRECT: Use static or malloc
int* goodFunction(void) {
static int x = 10;
return &x;
}
2. Not Checking Returned Pointer
// WRONG!
int *arr = createArray(100);
arr[0] = 10; // Crash if malloc failed!
// CORRECT
int *arr = createArray(100);
if (arr == NULL) {
fprintf(stderr, "Allocation failed\n");
return -1;
}
arr[0] = 10;
3. Wrong Pointer Type for Function Pointer
int add(int a, int b) { return a + b; }
// WRONG!
int *fp = add; // int* is not int(*)(int,int)
// CORRECT
int (*fp)(int, int) = add;
4. Forgetting to Dereference
void increment(int *p) {
p++; // WRONG! Increments the pointer
(*p)++; // CORRECT: Increments the value
}
5. Modifying const Through Cast
void func(const int *p) {
// WRONG! Undefined behavior
int *non_const = (int*)p;
*non_const = 100;
}
Best Practices
1. Use const for Input Parameters
// Good: clearly shows arr won't be modified
void analyze(const int *arr, int size);
2. Document Pointer Ownership
/**
* Creates an array of n integers.
* @param n Size of array
* @return Dynamically allocated array. Caller must free().
*/
int* createArray(int n);
3. Use typedef for Complex Function Pointers
// Hard to read
void (*signal(int, void (*)(int)))(int);
// Easier with typedef
typedef void (*SignalHandler)(int);
SignalHandler signal(int signum, SignalHandler handler);
4. Check All Pointer Parameters
int processData(int *data, int size) {
if (data == NULL || size <= 0) {
return -1; // Error
}
// Process...
return 0;
}
5. Initialize Output Parameters
void getValues(int *out1, int *out2) {
*out1 = 0; // Initialize to default
*out2 = 0;
// ... compute and set actual values
}
6. Prefer Passing Pointer for Large Structs
// Inefficient: copies entire struct
void processLarge(LargeStruct s);
// Efficient: only copies pointer
void processLarge(const LargeStruct *s);
Summary
Key Concepts
| Concept | Syntax | Purpose |
|---|---|---|
| Pass by pointer | void func(int *p) | Modify caller's variable |
| Return pointer | int* func() | Return address (careful!) |
| Function pointer | int (*fp)(int) | Store function address |
| Callback | void func(void (*cb)()) | Pass function as argument |
| Const pointer param | void func(const int *p) | Read-only access |
| Void pointer | void* | Generic pointer type |
When to Use Each
| Scenario | Technique |
|---|---|
| Modify a variable | Pass pointer parameter |
| Return multiple values | Pass multiple pointer parameters |
| Large struct as input | Pass const pointer |
| Select function at runtime | Function pointer |
| Custom sorting/processing | Callback function |
| Work with any type | void pointer |
Quick Reference
// Pass pointer to modify
void modify(int *p) { *p = 100; }
modify(&var);
// Return dynamic memory
int* create(int n) { return malloc(n * sizeof(int)); }
int *arr = create(10);
free(arr);
// Function pointer
int (*fp)(int, int) = add;
int result = fp(5, 3);
// Callback
void forEach(int *arr, int n, void (*cb)(int)) {
for (int i = 0; i < n; i++) cb(arr[i]);
}
// Multiple return values
void compute(int a, int b, int *sum, int *product) {
*sum = a + b;
*product = a * b;
}
Practice Exercises
- •Write a swap function using pointers
- •Implement a function that returns the address of the largest element in an array
- •Create a calculator using function pointers
- •Write a generic comparison function for qsort
- •Implement a callback-based event handler
See the exercises.c file for hands-on practice problems with solutions.