Pointers and Addresses
Chapter Thirty-Nine
Syllabus topic 3, "Pointer and Addresses, Pointer and Function Arguments, Pointer and Arrays."
Pages 188 to 193 of 222
In one line
Every object is somewhere in memory, that somewhere is its address, and a pointer is a variable that holds an address.
Why C has them at the front of the language
Chapter 33 showed a swap function that could not swap, because arguments are copied. That is the first reason: to let a function change something belonging to its caller, you give it the address rather than the value.
There are three more, and together they are most of what C is for.
- Arrays.
a[i]is defined in terms of pointer arithmetic, so an array and a pointer are closely related. Chapter 41. - Strings. A string is handled as a pointer to its first character. Chapter 37 used that without naming it.
- Memory you ask for while the program runs. A later semester's subject, and impossible without pointers.
The two operators
| Operator | Name | Given | Gives |
|---|---|---|---|
& | address-of | an object | its address |
* | indirection, or dereference | an address | the object at it |
They are opposites. *&x is x.
int x = 42;
int *p = &x; /* p holds the address of x */
printf("%d", *p); /* prints 42: the object p points at */The in int p is part of the declaration and says "p is a pointer to int". The in p = 7 is the operator and means "the object p points at". Same symbol, two jobs, and telling them apart is most of the difficulty of this chapter.
Read a declaration as a claim about the dereference: int p; says that p is an int. That reading scales to every declaration C can write.
Declaring one
int *p; /* pointer to int */
double *q; /* pointer to double */
char *s; /* pointer to char */
int *a, b; /* a is a pointer, b is an int */
int *a, *b; /* both pointers */The last two lines are why this book writes the next to the name rather than next to the type: int a, b; looks as though both are pointers and neither reading changes what the compiler does.
Seeing it work
#include <stdio.h>
int main(void)
{
int x = 42;
int *p = &x;
printf("x is %d\n", x);
printf("*p is %d, which is the same object\n", *p);
*p = 99; /* change x through p */
printf("after *p = 99, x is %d\n", x);
x = 7; /* change x directly */
printf("after x = 7, *p is %d\n", *p);
printf("&x and p hold the same address: %d\n", p == &x);
printf("*&x is %d, and &*p == &x is %d\n", *&x, &*p == &x);
printf("sizeof x is %zu, sizeof p is %zu\n", sizeof x, sizeof p);
return 0;
}Pointers and Addresses
x is 42
*p is 42, which is the same object
after *p = 99, x is 99
after x = 7, *p is 7
&x and p hold the same address: 1
*&x is 7, and &*p == &x is 1
sizeof x is 4, sizeof p is 8The two middle blocks are the point: x and *p are two names for one object. Changing either changes both, because there is only one thing there.
Addresses themselves
An address is a number, and on the machine this book is built on it is 8 bytes, which is sizeof p above. Printing one is done with %p and a cast to void *.
A printed address is not reproducible. Modern operating systems place a program at a different base address on every run, deliberately, so the same program prints different numbers each time. What is stable is the difference between the addresses of two objects in the same array, and that is what the next listing shows.
#include <stdio.h>
int main(void)
{
int a[5] = {10, 20, 30, 40, 50};
printf("sizeof(int) is %zu\n", sizeof(int));
printf("the gap between consecutive elements, in bytes:\n");
for (int i = 1; i < 5; i++) {
printf(" &a[%d] - &a[%d] = %ld byte(s)\n", i, i - 1,
(long) ((char *) &a[i] - (char *) &a[i - 1]));
}
printf("and in elements: &a[4] - &a[0] = %ld\n", (long) (&a[4] - &a[0]));
return 0;
}sizeof(int) is 4
the gap between consecutive elements, in bytes:
&a[1] - &a[0] = 4 byte(s)
&a[2] - &a[1] = 4 byte(s)
&a[3] - &a[2] = 4 byte(s)
&a[4] - &a[3] = 4 byte(s)
and in elements: &a[4] - &a[0] = 4Two subtractions, two different answers, and both are right. Subtracting char gives bytes; subtracting int gives elements. That is pointer arithmetic, and chapter 41 is built on it.
For completeness, here is a program that prints an address, and what happened when it was run three times in a row on Ubuntu 24.04 with gcc 13.3:
#include <stdio.h>
int main(void)
{
int x = 42;
int *p = &x;
printf("x is at %p\n", (void *) p);
return 0;
}$ cc -std=c17 -Wall -Wextra addresses.c -o addresses
$ ./addresses
x is at 0xffffdb4ba9ac
$ ./addresses
x is at 0xfffff0b91e9c
$ ./addresses
x is at 0xfffff37ed9fcThree runs of one unchanged program, three different addresses. Nothing is wrong: the operating system places the program somewhere different every time, on purpose, as a security measure. That is exactly why those lines are in a transcript rather than in an output block: this book proves what its programs print, and an address is the one thing it cannot. It is shown so that an address stops being an abstraction, and so that you are not puzzled when your own number differs from your neighbour's.
Pointers and Addresses
NULL
A pointer must point at something, and sometimes there is nothing to point at. NULL, from <stdio.h> and several other headers, is the value that means "points at nothing".
#include <stdio.h>
#include <string.h>
int main(void)
{
int *p = NULL;
printf("p == NULL is %d\n", p == NULL);
printf("as a condition, a null pointer is false: %d\n", p ? 1 : 0);
const char *text = "Programming with C";
char *found = strchr(text, 'z');
if (found == NULL) {
printf("strchr found no 'z', and said so by returning NULL\n");
}
found = strchr(text, 'w');
if (found != NULL) {
printf("strchr found 'w' at offset %ld\n", (long) (found - text));
}
return 0;
}p == NULL is 1
as a condition, a null pointer is false: 0
strchr found no 'z', and said so by returning NULL
strchr found 'w' at offset 12Dereferencing a null pointer is undefined behaviour and in practice kills the program. Every library function that returns a pointer and can fail returns NULL on failure, so the test is not optional:
char *found = strstr(text, key);
if (found != NULL) {
...
}A null pointer is false as a condition and any other pointer is true, so if (p) and if (p != NULL) are the same test. Write whichever you find clearer; this book writes the second in new code because it says what is being tested.
const and pointers
Two different things can be constant, and the difference matters as soon as you write a function.
const int *p; /* p may be changed; *p may not */
int *const p; /* p may not be changed; *p may */
const int *const p; /* neither */Read it right to left: const int p is "p is a pointer to a const int". int const p means the same as const int *p; both spellings are in use.
#include <stdio.h>
void print_all(const int *a, int n) /* promises not to change the array */
{
for (int i = 0; i < n; i++) {
printf("%d ", a[i]);
}
printf("\n");
}
void double_all(int *a, int n) /* is allowed to change it */
{
for (int i = 0; i < n; i++) {
a[i] = a[i] * 2;
}
}
int main(void)
{
int a[4] = {1, 2, 3, 4};
print_all(a, 4);
double_all(a, 4);
print_all(a, 4);
return 0;
}1 2 3 4
2 4 6 8const int * in a parameter is a promise to the caller, checked by the compiler. Use it on every pointer parameter a function does not write through. It costs nothing and it tells a reader which arguments can come back changed.
Pointers and Addresses
What goes wrong
An uninitialised pointer. int p; p = 5; writes to whatever address happened to be in p. Chapter 11's undefined behaviour, now able to damage anything. Set a pointer when you declare it, to a real address or to NULL.
Dereferencing NULL. Test before use.
A pointer to something that has gone. Returning the address of a local variable gives a pointer to memory that no longer belongs to anybody.
#include <stdio.h>
int *broken(void)
{
int local = 42;
return &local; /* the object dies when the function returns */
}
int main(void)
{
int *p = broken();
printf("p is not NULL: %d, and reading *p is undefined\n", p != NULL);
return 0;
}dangling.c: In function ‘broken’:
dangling.c:6:12: warning: function returns address of local variable [-Wreturn-local-addr]
6 | return &local; /* the object dies when the function returns */
| ^~~~~~The compiler catches this one, and the program is not run here: *p refers to an object whose lifetime ended. That is a dangling pointer, and the compiler's warning is the whole of what can honestly be shown.
What this does NOT mean
A pointer is not an integer. It holds an address, its arithmetic counts in elements rather than in bytes, and converting between the two is not portable.
in a declaration is not the dereference operator. In int p it declares a pointer; in *p = 5 it dereferences one.
& is not the bitwise AND here. As a unary operator it takes an address; as a binary operator it is bitwise AND.
NULL is not zero the number, exactly. It is a null pointer constant. Writing p = 0; is legal and means the same; NULL says what you meant.
A pointer does not have to be initialised, and must be. An uninitialised pointer holds an arbitrary address.
const int *p does not make p constant. It makes what p points at read-only through p.
An address printed once is not a fact about your machine. It changes between runs.
Quick revision
- An address is where an object is; a pointer is a variable holding an address.
&xgives the address ofx;*pgives the objectppoints at. They are opposites.int p;declarespsuch thatpis anint. Read the declaration as a claim about the dereference.int *a, b;declares one pointer and oneint.xand*pare two names for one object whenpis&x.- A pointer is 8 bytes on a 64-bit machine, and
%pneeds a cast tovoid *. - Subtracting two
intgives a count of elements; casting tocharfirst gives bytes. NULLmeans "points at nothing". Dereferencing it is undefined behaviour.- Every library function returning a pointer that can fail returns
NULL; test it. const int *ppromises not to write throughp. Use it on read-only parameters.- Never return the address of a local variable.
Pointers and Addresses
Test yourself
1. What do & and * do?
&x yields the address of x. *p yields the object at the address in p. Each undoes the other.
2. In int p = &x;, what is the type of p and what is the type of p?
p is int , a pointer to int. p is an int.
3. What does int *a, b; declare?
a as a pointer to int and b as an int. The * applies to one declarator only.
4. After int x = 5; int p = &x; p = 9; what is x?
xand*pare the same object.
5. Why must the result of strstr be tested against NULL?
Because strstr returns NULL when it does not find the text, and dereferencing a null pointer is undefined behaviour and normally kills the program.
6. What is the difference between const int p and int const p?
The first says the object may not be written through p, while p itself may be pointed elsewhere. The second says p may not be pointed elsewhere, while the object may be changed.
7. Why is returning &local from a function wrong?
The local object's lifetime ends when the function returns, so the returned address refers to memory that no longer holds it. That is a dangling pointer and reading through it is undefined behaviour.
What can be asked on this, and how to answer it
"What is a pointer? Explain with an example." Define it as a variable holding the address of another object. Give the declaration, & and , and a short program showing that x and p are the same object. Say that the in the declaration and the in the dereference are different uses of the symbol, because that is what confuses most answers.
"Explain the address-of and indirection operators." & applied to an object gives its address; applied to a pointer gives the object. They are inverse, so &x is x. Note that & cannot be applied to a constant or an expression, only to an object.
"What is a null pointer? What is its use?" A pointer holding NULL, which points at no object. It is used as a "nothing here" value, particularly as the failure return of library functions that give back pointers. Dereferencing it is undefined behaviour, so it must be tested first.
Pointers and Addresses
"What is a dangling pointer?" A pointer to an object whose lifetime has ended: the classic case is the address of a local variable returned from a function. Reading or writing through it is undefined behaviour.
"Distinguish between const int p and int const p." Give the right-to-left reading and one sentence each, as in Test yourself question 6, and add why the first is used on function parameters.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.