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

Arrays, functions and pointers13 min readLesson 3 of 4

3.1 An address in memory

Every variable occupies bytes in memory, and each byte has an address. A pointer is a variable that stores one of those addresses.

int x = 42; declares an int. int *p = &x; declares a pointer to int and stores the address where x lives.

&x is the address-of operator, and it produces a pointer to x. The * in the declaration is part of the type, so int *p reads as "p points to an int".

That reading matters when several names share one declaration. int *a, *b; declares two pointers, and int *a, b; declares one pointer and one plain int.

c
#include <stdio.h>

int main(void)
{
    int x = 42;
    int *p = &x;

    printf("x = %d\n", x);
    printf("*p = %d\n", *p);

    *p = 7;
    printf("x = %d\n", x);
    return 0;
}

Output

x = 42
*p = 42
x = 7

3.2 Dereferencing

The * operator on a pointer reads or writes the value at the address it holds. Applied to p from the example, *p is another name for x.

  • *p on the right of = reads the value
  • *p on the left of = writes a new value
  • &*p gives back p itself

Do not confuse the two uses of *. In a declaration it marks a pointer, and in an expression it dereferences one. int *p = &x; uses the declaration meaning on the left and no dereference on the right.

Parentheses matter when * meets ++. (*ptr)++ increments the value that ptr points to, and *ptr++ reads the old target and advances the pointer afterwards. Write the parentheses you mean.

c
#include <stdio.h>

int main(void)
{
    int count = 1;
    int *ptr = &count;

    *ptr = *ptr + 4;
    printf("count = %d\n", count);

    (*ptr)++;
    printf("count = %d\n", count);
    return 0;
}

Output

count = 5
count = 6

3.3 NULL and uninitialised pointers

A pointer that does not point anywhere should hold NULL, a constant that several headers define, including <stddef.h>.

Dereferencing a null pointer is undefined behaviour and usually stops the program. A check before use is cheap: if (p != NULL) { printf("%d\n", *p); }.

An uninitialised pointer is worse than a null one. It holds an arbitrary address, so a null check passes and the dereference writes to memory that belongs to something else. Initialise every pointer to NULL or to the address of a real object.

c
#include <stdio.h>

int main(void)
{
    int value = 9;
    int *chosen = &value;
    int *empty = NULL;

    if (chosen != NULL) {
        printf("chosen points to %d\n", *chosen);
    }
    if (empty == NULL) {
        printf("empty is not set\n");
    }
    return 0;
}

Output

chosen points to 9
empty is not set

3.4 Pointers as parameters

Passing a pointer lets a function reach the caller's variable. The parameter copies the address, and a dereference inside the function writes through it.

void reset(int *p) { *p = 0; } sets the caller's variable to 0 when called as reset(&x);. Inside the function, *p names the same storage as x.

The usual C pattern for returning more than one result is to take pointers for the outputs and use the return value for a status. scanf works this way: it takes addresses so it can fill several variables and reports how many it filled.

c
#include <stdio.h>

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

int main(void)
{
    int x = 3;
    int y = 8;

    swap(&x, &y);
    printf("x = %d, y = %d\n", x, y);
    return 0;
}

Output

x = 8, y = 3

3.5 Pointers and arrays

An array name in an expression becomes a pointer to its first element. int a[4]; gives you a and &a[0] pointing to the same address, and *a is the same value as a[0].

Indexing and pointer arithmetic are defined in terms of each other:

  • a[i] is the value at a + i
  • *(a + i) is the same value
  • adding 1 to an int * moves by sizeof(int) bytes

Pointer arithmetic is only defined inside an array or one element past its end. Moving outside that range is undefined behaviour, even when the address happens to look valid. The exceptions to the array name rule are sizeof a and &a, which keep the array type.

c
#include <stdio.h>

int main(void)
{
    int values[4] = {5, 10, 15, 20};
    int *p = values;

    printf("%d %d\n", p[0], p[3]);
    printf("%d\n", *(p + 2));
    printf("%d\n", *p + 1);
    return 0;
}

Output

5 20
15
6

3.6 const on pointers

Two different consts can appear in a pointer declaration:

  • const int *p means the pointed-to data cannot be changed through p
  • int *const p means the pointer itself cannot be reassigned
  • const int *const p fixes both

Read the declaration from the name outward. The position of const relative to * decides which part it protects. const does not stop another pointer from changing the same object.

c
#include <stdio.h>

int main(void)
{
    int value = 10;
    const int *read_only = &value;
    int *const fixed = &value;

    *fixed = 11;               /* allowed: the data is not const */
    printf("value = %d\n", value);
    printf("read through a pointer to const: %d\n", *read_only);
    return 0;
}

Output

value = 11
read through a pointer to const: 11

3.7 Pointer mistakes to avoid

  • using a pointer before it points to a real object
  • dereferencing NULL
  • keeping a pointer to a local variable after its function has returned, because the variable is gone and the pointer no longer points to valid storage
  • writing through a pointer to data that was declared const
  • pointing an int * at a double and then wondering why the value looks wrong; the compiler warns with -Wincompatible-pointer-types, and a cast hides the warning without fixing the mismatch

Interview questions

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

  • 01What does `*` mean in `int *p` and in `*p = 5`?

    In the declaration it makes `p` a pointer to `int`. In the expression it dereferences `p`, so `*p = 5` stores 5 at the address `p` holds.

  • 02Why does `scanf("%d", &x)` need the `&`?

    `scanf` has to write into `x`, and C passes arguments by value. The `&` passes the address, so `scanf` can store the number in the caller's variable.

  • 03What is a null pointer, and why check for one?

    A null pointer holds a value that does not refer to any object, usually written `NULL`. Dereferencing it is undefined behaviour, and a check before use prevents a crash.