xSKILLby Infirals
Register

C for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Pointers to pointers and arrays of pointers

Pointers and memory12 min readLesson 2 of 6

2.1 A pointer with an address of its own

A pointer variable occupies memory like any other variable, so it has an address. Taking that address gives a pointer to a pointer.

int value = 7; int *p = &value; int **pp = &p; builds three levels. value holds a number, p holds the address of value, and pp holds the address of p.

Reading follows the same path backwards. *pp is p, and **pp is value. Each * removes one level of indirection, and the declaration says how many levels exist.

c
#include <stdio.h>

int main(void)
{
    int value = 7;
    int *p = &value;
    int **pp = &p;

    printf("value: %d\n", value);
    printf("*p: %d\n", *p);
    printf("**pp: %d\n", **pp);
    printf("same address: %d\n", *pp == p);
    return 0;
}

Output

value: 7
*p: 7
**pp: 7
same address: 1

2.2 Changing the caller's pointer

C passes arguments by value, so a function receives a copy of the pointer. Assigning a new address to the parameter changes the copy only.

Passing the address of the pointer fixes that. The parameter is int **, and *chosen = other; writes through it to the caller's variable.

This shape appears when a function picks one object from several, or when a function allocates memory and hands the pointer back. The return value is then available for a status code.

c
#include <stdio.h>

static void point_at_larger(int **chosen, int *a, int *b)
{
    *chosen = (*a > *b) ? a : b;
}

int main(void)
{
    int x = 4;
    int y = 11;
    int *pick = &x;

    point_at_larger(&pick, &x, &y);
    printf("picked: %d\n", *pick);
    return 0;
}

Output

picked: 11

2.3 Reading the declaration

C declarations read from the name outward. Start at pp in int **pp, step right, then left. pp is a pointer to a pointer to an int.

The rule scales to shapes that appear in real headers. char *names[3] is an array of three pointers to char. char (*names)[3] is a pointer to an array of three char. The parentheses move the emphasis.

When a declaration is hard to read, build a typedef and declare from that. The compiler cares about the types; a reader should not have to parse them twice.

c
#include <stdio.h>

int main(void)
{
    char row[3] = {'a', 'b', 'c'};
    char (*block)[3] = &row;
    char *letters[3] = {&row[0], &row[1], &row[2]};

    printf("%c\n", (*block)[1]);
    printf("%c\n", *letters[2]);
    printf("array bytes: %zu\n", sizeof row);
    return 0;
}

Output

b
c
array bytes: 3

2.4 An array of pointers to text

String literals have different lengths, so they do not fit a rectangular char array without wasted space. An array of char * stores one address per string and lets each literal keep its own size.

const char *names[] = {"Ada", "Grace", "Linus"}; creates three pointers. The literals themselves live in read-only storage, so the elements are declared const and the compiler rejects a write through them.

The element type is const char *. names[i] is a pointer, and names[i][0] is the first character of that string.

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

int main(void)
{
    const char *names[] = {"Ada", "Grace", "Linus"};
    int count = (int)(sizeof names / sizeof names[0]);

    for (int i = 0; i < count; i++) {
        printf("%d: %s (%zu)\n", i + 1, names[i], strlen(names[i]));
    }
    return 0;
}

Output

1: Ada (3)
2: Grace (5)
3: Linus (5)

2.5 Sorting pointers instead of strings

Swapping two pointers costs a few bytes regardless of the text length. Sorting an array of const char * leaves the strings where they are and reorders the addresses.

A selection sort shows the mechanics. Compare two entries with strcmp, then swap the pointers when they are out of order. The characters never move and no extra buffer is needed.

The array itself is not const, even though each element points at const data. Reordering changes which address sits in each slot, and that is allowed.

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

int main(void)
{
    const char *names[] = {"Linus", "Ada", "Grace"};
    for (int i = 0; i < 3; i++) {
        for (int j = i + 1; j < 3; j++) {
            if (strcmp(names[i], names[j]) > 0) {
                const char *temp = names[i];
                names[i] = names[j];
                names[j] = temp;
            }
        }
    }
    for (int i = 0; i < 3; i++) {
        printf("%s\n", names[i]);
    }
    return 0;
}

Output

Ada
Grace
Linus

2.6 argv is a char **

Every program that takes arguments receives argv, and its type explains the extra level. argv points at the first element of an array, and each element is a char * pointing at text.

So argv is char **: a pointer to the first char *. argv[0] is the program name, argv[i] is an argument, and argv[argc] is a null pointer by the standard's guarantee.

A function that prints a list of strings takes the same type plus a count. That signature works for argv and for any other array of strings.

c
#include <stdio.h>

static void print_lines(const char **lines, int count)
{
    for (int i = 0; i < count; i++) {
        printf("[%d] %s\n", i, lines[i]);
    }
}

int main(void)
{
    const char *lines[] = {"alpha", "beta"};
    print_lines(lines, 2);
    return 0;
}

Output

[0] alpha
[1] beta

2.7 Where the extra level is worth it

Functions that produce a pointer often need the double form, because the return value already carries success or failure. A function can return 0 or an error code while *out receives the allocated buffer.

Arrays of pointers are worth it when rows have different widths: strings, sparse tables, and lists with optional entries. A rectangular array stays simpler when every row has the same size.

Add another level only when the function must change the pointer. A function that only reads a string needs const char *, and another * makes the caller's job harder.

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

static int pick_longer(const char *a, const char *b, const char **out)
{
    if (a == NULL || b == NULL) {
        return -1;
    }
    *out = (strlen(a) >= strlen(b)) ? a : b;
    return 0;
}

int main(void)
{
    const char *result = NULL;
    if (pick_longer("cat", "horse", &result) == 0) {
        printf("longer: %s\n", result);
    }
    return 0;
}

Output

longer: horse

Interview questions

3 questions from this note. Open one to see the answer; no account needed.

  • 01What does `int **pp` mean?

    `pp` is a pointer to a pointer to `int`. `*pp` gives an `int *`, and `**pp` gives the `int` itself.

  • 02Why pass `&p` when a function needs to change the pointer `p`?

    The function receives a copy of its argument. Passing the address of `p` lets the function write a new pointer value into the caller's variable.

  • 03Why can an array of `const char *` be reordered?

    The elements are pointers, and the pointer values are not const. Reordering changes which address sits in each slot while the characters stay read-only.