CPassing Pointers to Functions

Passing Pointers to Functions

C passes every argument by value: the function receives a copy. That means a plain int parameter can never change the caller's variable — but a pointer parameter is a copy of an address, and following that copied address still leads back to the caller's original data. Passing pointers is how C functions modify their caller's variables, work with arrays and large structs efficiently, and produce more than one output value.

Recap: modifying a caller's variable

Passing &x to a function that takes an int * lets the function dereference that pointer to read or write the original x. This is the fundamental trick behind every "output parameter" in C, from scanf("%d", &n) to custom functions like the one below.

C
#include <stdio.h>

void increment(int *x) {
    (*x)++;      // dereference, then increment the caller's variable
}

int main(void) {
    int n = 5;
    increment(&n);
    printf("%d\n", n);
    return 0;
}
6
Arrays decay to pointers when passed

An array argument is not copied when passed to a function — it decays to a pointer to its first element, so the function operates on the caller's actual array. This is efficient (no copying, even for huge arrays) but means the function must be told the length separately, since the pointer alone carries no size information.

C
#include <stdio.h>

void double_values(int *arr, int length) {
    for (int i = 0; i < length; i++) {
        arr[i] *= 2;    // modifies the caller's array directly
    }
}

int main(void) {
    int nums[4] = {1, 2, 3, 4};
    double_values(nums, 4);

    for (int i = 0; i < 4; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");

    return 0;
}
2 4 6 8
Passing structs by pointer avoids copying

A struct passed by value is copied field-by-field, which can be expensive for large structs and, more importantly, means the function cannot modify the caller's original. Passing a pointer to the struct avoids the copy entirely and lets the function modify the caller's data in place.

Worked example: modifying a struct through a pointer

C
#include <stdio.h>

typedef struct {
    char name[32];
    int hp;
    int level;
} Character;

// Takes a pointer, so it modifies the caller's Character directly
// instead of a throwaway copy.
void level_up(Character *c) {
    c->level += 1;
    c->hp += 10;
}

void print_character(const Character *c) {
    printf("%s: level %d, hp %d\n", c->name, c->level, c->hp);
}

int main(void) {
    Character hero = {"Hero", 100, 1};

    print_character(&hero);
    level_up(&hero);
    level_up(&hero);
    print_character(&hero);

    return 0;
}
Hero: level 1, hp 100
Hero: level 3, hp 120
The arrow operator is shorthand for (*c).field
c->level is exactly equivalent to `(*c).level`: dereference the pointer, then access the field. The arrow operator is just a more readable shorthand for the extremely common pattern of accessing a field through a struct pointer.
const documents read-only intent
`print_character` takes `const Character *c` because it only reads the struct — the `const` both documents that promise and lets the compiler enforce it.
Never pass a pointer to something that no longer exists
Whether it's a local variable, an array, or a struct, the pointer is only valid as long as the thing it points to is valid. Passing (or returning) a pointer to a local variable whose function has already returned is undefined behavior — see NULL & Dangling Pointers for the details.
  • C passes arguments by value, so pointers are the mechanism for a function to modify its caller's data.

  • Arrays decay to a pointer when passed to a function — pass the length separately, since the pointer carries no size information.

  • Passing a struct by pointer avoids copying it and allows the function to modify the original.

  • Use const on pointer parameters that a function only reads, never writes.