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.
#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.
*pon the right of=reads the value*pon the left of=writes a new value&*pgives backpitself
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.
#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.
#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.
#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 ata + i*(a + i)is the same value- adding 1 to an
int *moves bysizeof(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.
#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 *pmeans the pointed-to data cannot be changed throughpint *const pmeans the pointer itself cannot be reassignedconst int *const pfixes 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.
#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 adoubleand then wondering why the value looks wrong; the compiler warns with-Wincompatible-pointer-types, and a cast hides the warning without fixing the mismatch
