Chapter 10 of 24

10. Pointers

A value that identifies a location

A pointer is a value that can identify the location of another object in memory. It is not the object itself: think of the object as a house and the pointer as an address that lets code find it.

  1. Declare a pointer with the type of object it may point to, such as int *p.
  2. Use &value to get the address of an existing object.
  3. Use *p to access the pointed-to value only when p is valid and points to a live object.

A null pointer represents “no object”; it cannot be dereferenced. Start with pointers to ordinary local variables before applying them to arrays or dynamically allocated memory.

A pointer stores an address. The pointer's type describes what kind of object
it points to.

#include <stdio.h>

int main(void)
{
    int value = 42;
    int *pointer = &value;
    printf("%d\n", *pointer);
    return 0;
}

Initialize pointers before use. A null pointer points to no object:

#include <stddef.h>

int main(void)
{
    int *pointer = NULL;
    return pointer == NULL ? 0 : 1;
}

Do not dereference a null, uninitialized, expired, or otherwise invalid
pointer. A pointer must have the correct type and point to a live object before
it is used.

Arrays and pointers are closely related in many expressions, but they are not
the same thing. An array expression often converts to a pointer to its first
element. Pointer arithmetic is only valid within an array object (or one past
its end); the one-past pointer may not be dereferenced.

const and pointers

Function parameters that accept read-only arrays commonly use const.

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

    (void)read_only;  // *read_only = 20 is not allowed. 
    *fixed_address = 20;
    return value == 20 ? 0 : 1;
}

The pointer fixed_address itself cannot be assigned a different address
after initialization.

Following an address step by step

If pointer contains the address of value, then *pointer designates the
same object as value. Assigning through it changes that object:

int main(void)
{
    int value = 10;
    int *pointer = &value;
    *pointer = 25;
    return value == 25 ? 0 : 1;
}

The pointer itself is a separate variable. Changing pointer to another
valid address does not change value; changing *pointer changes the object
at the address. This distinction is the basis for output parameters, arrays,
linked structures, and dynamic allocation.

Pointer preconditions

Before dereferencing, establish that a pointer is non-null and points to a
live object of the expected type. A null check alone is not enough if a
pointer has already expired or points outside the correct object. Never
return the address of a local automatic variable. When a pointer refers to
one array element, pointer arithmetic may move within that same array or to
one past its end, but not beyond; the one-past pointer is only for comparisons
or iteration termination, not dereferencing.

#include <stdbool.h>
#include <stddef.h>

bool read_value(const int *pointer, int *output)
{
    if (pointer == NULL || output == NULL) {
        return false;
    }

    *output = *pointer;
    return true;
}

The null checks establish only part of the contract: callers must also
provide pointers to live, correctly typed objects.

Pointers and arrays are connected by the rule that an array expression often
converts to a pointer to its first element. The array still has its own size
and cannot be reassigned; a pointer is an object that stores an address and
can point elsewhere. For each pointer in your program, ask: What object does
it point to? How long does that object live? Who may modify it?

Checkpoint: Trace pointer, *pointer, and value through the example.
State two reasons a pointer might be invalid even if it is not NULL.

Pointer declarations and reading them

The * in a declaration binds to the declarator, not to the base type. Thus
int *first, second; declares first as a pointer to int and second as
an int. For clarity, prefer one declaration per line when pointer types are
involved:

#include <stddef.h>

int *first;
int second;

Read a declaration from the identifier outward. In int *p, p is a
pointer to int. In int **pp, pp is a pointer to a pointer to int.
Parentheses matter: int *rows[4] is an array of four pointers, while
int (*row)[4] is a pointer to an array of four integers.

Pointer arithmetic

Adding one to a pointer advances by one pointed-to element, not one byte:
p + 1 points to the next int when p is an int *. Pointer subtraction
is defined only for pointers into the same array object (or one past it) and
produces a ptrdiff_t. Comparisons between unrelated object pointers do not
provide a portable ordering.

These rules make a pointer plus a length a common way to describe a range.
The beginning points at the first element and the end points one past the
last. A range is empty when begin equals end; the end is never dereferenced.
Do not form a pointer beyond one-past, even if it is not dereferenced.

#include <stddef.h>

int sum_range(const int *begin, const int *end)
{
    int total = 0;
    while (begin != end) {
        total += *begin;
        ++begin;
    }
    return total;
}

This requires begin and end to describe a valid range in the same array,
with end at or one past the final element. The accumulator also needs an
application-appropriate overflow policy.

void * and generic object pointers

void * can hold a pointer to an object of any type and can be converted
back to the original object-pointer type. It has no element type, so it
cannot be dereferenced or incremented until converted to an appropriate
object pointer. Standard allocation functions return void *; in C, that
value converts to an object pointer type without a cast. Avoid unnecessary
casts of malloc results because they can hide a missing declaration.

Function pointers are not interchangeable with object pointers. A callback
must use a compatible function type. When a generic library accepts void *
context data, document how long the context must remain valid and how the
callback interprets it.

#include <stdio.h>

int main(void)
{
    int value = 42;
    void *generic = &value;
    int *integer_pointer = generic;  // Object-pointer conversion in C. 
    printf("%d\n", *integer_pointer);
    return 0;
}

Convert back to the original compatible object-pointer type before
dereferencing; void * itself has no type to dereference.

Pointer-to-pointer parameters

A pointer-to-pointer is useful when a function must change the caller's
pointer, for example to install a newly allocated object:

#include <stdbool.h>
#include <stdlib.h>

bool make_int(int **output)
{
    if (output == NULL) {
        return false;
    }

    int *created = malloc(sizeof *created);
    if (created == NULL) {
        return false;
    }

    *created = 0;
    *output = created;
    return true;
}

The caller can initialize its pointer to NULL, call the function with
&pointer, and free the result if successful. This interface transfers
ownership to the caller; that obligation must be documented. Do not use
T ** merely because an array parameter is present: an array of T is
passed as a T *, while T ** means a pointer to a T *.

Const correctness

Read pointer types from the identifier:

Use const on input-only pointers to communicate intent and allow callers
to pass read-only data. const does not make the underlying object immutable
through every possible alias; it restricts modification through that access
path.

#include <stddef.h>

int sum_values(const int values[], size_t length)
{
    int sum = 0;
    for (size_t i = 0; i < length; ++i) {
        sum += values[i];
    }
    return sum;
}

The parameter prevents this function from modifying array elements through
values. The caller must pass a valid range, and the sum must fit in int.

Exercises: draw a memory diagram for int *p and int **pp; write a
function that swaps two integers; write a function that takes a pointer and a
length and sums the elements without modifying them.

Exercises: swap two integers using pointers; write a function that finds
an element and returns a pointer to it or NULL; explain the difference
between an array and a pointer.


Suggested topics

0% read