Pointers
A pointer holds the address of an object. Through it a function can read and change memory that belongs to its caller, which is the mechanism behind the out-parameters of the C library. The basics are collected on Pointers in C; this lesson builds the working habits the rest of the course uses.
Address and dereference
&value produces the address of an object, and *p names the object at that address. The two operations are inverses: a pointer initialised with &value reads and writes exactly that variable. Printing the address needs %p and a cast to void *.
#include <stdio.h>
int main(void)
{
int value = 42;
int *p = &value;
printf("value: %d\n", value);
printf("*p: %d\n", *p);
printf("address: %p\n", (void *) p);
*p = 7;
printf("value now: %d\n", value);
return 0;
}
gcc -Wall -Wextra addr.c -o addr
./addr
value: 42
*p: 42
address: 0x7ffef8b8f0cc
value now: 7
The address changes on every run because the system places the stack differently; the first, second and last lines are the stable result. Writing through *p changed value, which proves the pointer was not a copy.
NULL and the validity check
NULL is the address that points to no object, and dereferencing it crashes the process. A pointer that may be absent is tested before use. The check is cheap and makes the failure a message instead of a segmentation fault.
#include <stdio.h>
int main(void)
{
int *p = NULL;
if (p != NULL) {
printf("%d\n", *p);
} else {
printf("null pointer, not dereferenced\n");
}
return 0;
}
gcc -Wall -Wextra null.c -o null
./null
null pointer, not dereferenced
The comparison is written out rather than abbreviated: if (p) means the same, but the explicit form tells a reader that p is a pointer, not a number.
Pointers as out-parameters
Lesson 17 showed that a parameter is a copy, so a function cannot change its caller's variable directly. Passing the address changes that: the function receives a copy of the pointer, but the copy points at the caller's object. The classic example swaps two integers.
#include <stdio.h>
static void swap(int *a, int *b)
{
int temporary = *a;
*a = *b;
*b = temporary;
}
int main(void)
{
int x = 1;
int y = 2;
swap(&x, &y);
printf("x: %d, y: %d\n", x, y);
return 0;
}
gcc -Wall -Wextra swap.c -o swap
./swap
x: 2, y: 1
The temporary exists because the first assignment overwrites *a; without it both variables would end with the same value. The parameters are pointers, so the call passes addresses, and a mistake such as swap(x, y) fails to compile.
More than one result
A function returns one value, so a second result travels through a pointer. Division is the standard case: the quotient and the remainder are both wanted, and the caller declares two variables and passes their addresses. The function writes through the pointers and returns nothing.
#include <stdio.h>
static void divide(int a, int b, int *quotient, int *remainder)
{
*quotient = a / b;
*remainder = a % b;
}
int main(void)
{
int q = 0;
int r = 0;
divide(17, 5, &q, &r);
printf("17 / 5 = %d remainder %d\n", q, r);
return 0;
}
gcc -Wall -Wextra divmod.c -o divmod
./divmod
17 / 5 = 3 remainder 2
The caller initialises both outputs, which is not required by the function but makes a missed write visible. A function that can fail usually returns a status and writes its result through a pointer, the pattern of scanf from lesson 10.
An integer is not an address
An integer constant cannot initialise a pointer without a cast, and GCC 15 treats the mismatch as an error rather than a warning. The message names both types and the exact character:
#include <stdio.h>
int main(void)
{
int *p = 42;
printf("%d\n", *p);
return 0;
}
gcc -Wall -Wextra badint.c -o badint
badint.c: In function 'main':
badint.c:5:14: error: initialization of 'int *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
5 | int *p = 42;
| ^~
The second mistake is passing the pointer to %d instead of dereferencing it. The format expects an int and receives an address, and the compiler suggests the conversion that matches a pointer:
#include <stdio.h>
int main(void)
{
int value = 5;
int *p = &value;
printf("%d\n", p);
return 0;
}
gcc -Wall -Wextra badfmt.c -o badfmt
badfmt.c: In function 'main':
badfmt.c:8:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'int *' [-Wformat=]
8 | printf("%d\n", p);
| ~^ ~
| | |
| | int *
| int
| %ls
The suggested %ls is not the fix; it matches a wide string. The intended conversion is *p with %d, or (void *) p with %p when the address itself is the subject.
Exercises
- Write a function that takes two pointers and stores the larger value in the first and the smaller in the second.
- Change the division function to reject a zero divisor through its return value, and show both the success and the failure call.
- Print the address of a local variable, a static variable and a function with %p, and describe what the three addresses tell you about the layout of the program.
Questions
A new set of questions in a new order every time. Choose one answer for each, then check.
-
How do
&and*relate?Answer
&takes an address,*reads through itThe two operators are inverses: a pointer initialised with
&valuereads and writes that same object through*p. -
Why is a pointer checked against
NULLbefore it is dereferenced?Answer
Because
NULLmeans no object and dereferencing it crashesNULLpoints to no object, so a dereference is undefined and usually ends in a segmentation fault; the check turns it into a message. -
How can a function change a variable that belongs to its caller?
Answer
By receiving the address of the variable
The parameter is still a copy, but a copy of the address points at the caller object, so writes through it are visible outside.
-
How does a function return two results?
Answer
Through an out-parameter pointer
One value comes from
return, and the second is written through a pointer the caller supplies. -
What does this program print?
#include <stdio.h> int main(void) { int value = 42; int *p = &value; printf("%d\n", *p); return 0; }Answer
42
pholds the address ofvalue, so*pnames that object and prints its value. -
What does this program print?
#include <stdio.h> int main(void) { int value = 42; int *p = &value; *p = 7; printf("%d\n", value); return 0; }Answer
7
Writing through the pointer changes the pointee, so the original variable now holds 7.
-
What does this program print?
#include <stdio.h> static void swap(int *a, int *b) { int temporary = *a; *a = *b; *b = temporary; } int main(void) { int x = 1, y = 2; swap(&x, &y); printf("x: %d, y: %d\n", x, y); return 0; }Answer
x: 2, y: 1
The function swaps the values through the addresses, so the caller sees 2 and 1.
-
What does this program print?
#include <stdio.h> static void divide(int a, int b, int *quotient, int *remainder) { *quotient = a / b; *remainder = a % b; } int main(void) { int q = 0, r = 0; divide(17, 5, &q, &r); printf("17 / 5 = %d remainder %d\n", q, r); return 0; }Answer
17 / 5 = 3 remainder 2
Integer division gives quotient 3 and remainder 2, and both are written through the pointers.
-
This program is built with
gcc -std=c17 -Wall -Wextra badint.c -o badint. Which error does GCC print?#include <stdio.h> int main(void) { int *p = 42; printf("%d\n", *p); return 0; }Answer
makes pointer from integer
The integer initialiser for
int *pis the error described as making a pointer from an integer. -
This program is built with
gcc -std=c17 -Wall -Wextra badfmt.c -o badfmt. Which warning flag does GCC print?#include <stdio.h> int main(void) { int value = 5; int *p = &value; printf("%d\n", p); return 0; }Answer
-Wformat=
%dagainst anint *is a format mismatch, reported by-Wformat=.
The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.
Article author: Arthur Isaev