Bytebook Compiler

Pointers & Memory

Pointers, Arrays & Functions

Call by reference, the swap function, returning several results, how arrays decay to pointers, and pointers to strings.

6 min read6 runnable examples4-question quiz

Call by reference

Functions receive copies of their arguments. To let a function change the caller's variable, pass its address, and have the function write through the pointer:

C
#include <stdio.h>

void swapWrong(int a, int b) { int t = a; a = b; b = t; }
void swap(int *a, int *b)    { int t = *a; *a = *b; *b = t; }

int main(void) {
    int x = 3, y = 8;
    swapWrong(x, y);
    printf("after swapWrong: x = %d, y = %d\n", x, y);
    swap(&x, &y);
    printf("after swap:      x = %d, y = %d\n", x, y);
    return 0;
}
Output
after swapWrong: x = 3, y = 8
after swap:      x = 8, y = 3

Returning several results

A function can return only one value, but it can fill as many variables as you give it addresses of:

C
#include <stdio.h>

void stats(const int a[], int n, int *min, int *max, double *avg) {
    *min = *max = a[0];
    int sum = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] < *min) *min = a[i];
        if (a[i] > *max) *max = a[i];
        sum += a[i];
    }
    *avg = (double)sum / n;
}

int main(void) {
    int marks[] = {67, 91, 54, 78, 85};
    int lo, hi;
    double mean;
    stats(marks, 5, &lo, &hi, &mean);
    printf("min %d, max %d, average %.1f\n", lo, hi, mean);
    return 0;
}
Output
min 54, max 91, average 75.0

scanf("%d", &n) works exactly this way — now you know why it needs &.

Arrays and pointers

An array's name, used in an expression, decays to a pointer to its first element. These are all equivalent:

Array notation Pointer notation
a[i] *(a + i)
&a[i] a + i
a[0] *a
C
#include <stdio.h>

int sum(int *p, int n) {          // same as int p[]
    int s = 0;
    for (int i = 0; i < n; i++) s += *(p + i);
    return s;
}

int main(void) {
    int a[] = {5, 10, 15, 20};
    printf("a[2] = %d, *(a + 2) = %d, 2[a] = %d\n", a[2], *(a + 2), 2[a]);
    printf("sum = %d\n", sum(a, 4));
    printf("sum of last two = %d\n", sum(a + 2, 2));   // pass part of an array
    return 0;
}
Output
a[2] = 15, *(a + 2) = 15, 2[a] = 15
sum = 50
sum of last two = 35

(2[a] works because a[i] is defined as *(a + i), and addition does not care about order. Fun fact, never write it in real code.)

Pointers and strings

A string literal can be referenced by a char *. Walking a string with a pointer is a very common C idiom:

C
#include <stdio.h>

int length(const char *s) {
    const char *start = s;
    while (*s) s++;               // stop at '\0'
    return (int)(s - start);
}

void toUpper(char *s) {
    for (; *s; s++)
        if (*s >= 'a' && *s <= 'z') *s -= 32;
}

int main(void) {
    char word[] = "pointer";
    printf("length = %d\n", length(word));
    toUpper(word);
    printf("upper  = %s\n", word);

    const char *greeting = "hello";   // points to read-only memory
    printf("%c%c\n", *greeting, *(greeting + 4));
    return 0;
}
Output
length = 7
upper  = POINTER
ho

Arrays of pointers

An array of char * is the usual way to store a list of strings of different lengths:

C
#include <stdio.h>
#include <string.h>

int main(void) {
    const char *subjects[] = {"C", "Data Structures", "Discrete Mathematics", "Python"};
    int n = sizeof(subjects) / sizeof(subjects[0]);
    for (int i = 0; i < n; i++)
        printf("%d. %s (%zu chars)\n", i + 1, subjects[i], strlen(subjects[i]));
    return 0;
}
Output
1. C (1 chars)
2. Data Structures (15 chars)
3. Discrete Mathematics (20 chars)
4. Python (6 chars)

Function pointers

Functions have addresses too. A function pointer lets you pass behaviour as an argument — this is how qsort lets you choose the sort order:

C
#include <stdio.h>
#include <stdlib.h>

int ascending(const void *a, const void *b)  { return *(const int *)a - *(const int *)b; }
int descending(const void *a, const void *b) { return *(const int *)b - *(const int *)a; }

void show(const int *a, int n) { for (int i = 0; i < n; i++) printf("%d ", a[i]); printf("\n"); }

int main(void) {
    int a[] = {42, 7, 19, 3, 88, 25};
    int n = 6;
    qsort(a, n, sizeof(int), ascending);
    show(a, n);
    qsort(a, n, sizeof(int), descending);
    show(a, n);

    int (*op)(const void *, const void *) = ascending;   // a variable holding a function
    printf("op(&a[0], &a[1]) = %d\n", op(&a[0], &a[1]) > 0);
    return 0;
}
Output
3 7 19 25 42 88 
88 42 25 19 7 3 
op(&a[0], &a[1]) = 1

Test yourself

4 questions · pick an answer to check it instantly

  1. 1

    Which call lets void inc(int *p) { (*p)++; } change n in main?

  2. 2

    For int a[5];, which expression is the same as a[3]?

  3. 3

    Why does char *s = "hi"; s[0] = 'H'; usually crash?

  4. 4

    Inside void f(int a[]), what does sizeof(a) give on a 64-bit machine?