Docs

Pointers and Functions

Pointers and Functions

Table of Contents

  1. Introduction
  2. Passing Pointers to Functions
  3. Call by Value vs Call by Reference
  4. Returning Pointers from Functions
  5. Pointer to Functions
  6. Array of Function Pointers
  7. Callback Functions
  8. Functions Returning Multiple Values
  9. Const Pointers in Functions
  10. Generic Functions with void Pointers
  11. Common Mistakes and Pitfalls
  12. Best Practices
  13. Summary

Introduction

Pointers and functions work together to provide powerful capabilities in C:

  1. Modify variables in the caller's scope (pass by reference)
  2. Return multiple values from a function
  3. Pass large data efficiently without copying
  4. Create callback mechanisms for flexible designs
  5. 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

AspectCall by ValueCall by Reference
What's passedCopy of valueAddress of variable
Original modifiedNoYes
Memory overheadCopy createdOnly pointer (8 bytes)
Syntaxfunc(var)func(&var)
Parameter typetype vartype *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

SourceSafe?Notes
Static variable✅ YesPersists after function returns
Global variable✅ YesPersists for program lifetime
malloc/calloc✅ YesCaller must free
Parameter pointer✅ YesPoints to caller's data
Local variable❌ NoDestroyed after return
Local array❌ NoDestroyed 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

ConceptSyntaxPurpose
Pass by pointervoid func(int *p)Modify caller's variable
Return pointerint* func()Return address (careful!)
Function pointerint (*fp)(int)Store function address
Callbackvoid func(void (*cb)())Pass function as argument
Const pointer paramvoid func(const int *p)Read-only access
Void pointervoid*Generic pointer type

When to Use Each

ScenarioTechnique
Modify a variablePass pointer parameter
Return multiple valuesPass multiple pointer parameters
Large struct as inputPass const pointer
Select function at runtimeFunction pointer
Custom sorting/processingCallback function
Work with any typevoid 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

  1. Write a swap function using pointers
  2. Implement a function that returns the address of the largest element in an array
  3. Create a calculator using function pointers
  4. Write a generic comparison function for qsort
  5. Implement a callback-based event handler

See the exercises.c file for hands-on practice problems with solutions.

Pointers And Functions - C Programming Tutorial | DeepML