Pointers, NULL & nullptr
Pointers are the feature that makes C powerful, and the one that worries beginners most. The idea is simple: every variable lives somewhere in memory, and that place has an address, a number. A pointer is a variable that stores an address.
With pointers, a function can change the caller's variables, large data can be shared without copying, and you can build structures such as linked lists. In this lesson you will learn the two pointer operators, use pointers with functions, and learn the safe way to say "points to nothing" with C23's nullptr.
Addresses and the two pointer operators
Think of memory as a long row of numbered boxes, one per byte. When you declare int marks = 87;, the compiler reserves four boxes for marks, say starting at box 1000. Two operators work with addresses:
&(address-of) gives the address of a variable:&marksis 1000.*(dereference) follows an address to the value stored there: ifpholds 1000, then*pis 87.
A pointer is declared with a * between the type and the name. int *p means "p holds the address of an int".
#include <stdio.h>
int main(void)
{
int marks = 87;
int *p = &marks; // p holds the address of marks
printf("marks = %d\n", marks);
printf("&marks = %p\n", (void *) &marks);
printf("p = %p\n", (void *) p);
printf("*p = %d\n", *p);
*p = 92; // change marks through the pointer
printf("marks = %d\n", marks);
printf("sizeof p = %zu\n", sizeof p);
return 0;
}A typical run on 64-bit Linux prints:
marks = 87 &marks = 0x7ffc8a2e4b1c p = 0x7ffc8a2e4b1c *p = 87 marks = 92 sizeof p = 8
The addresses on your computer will be different, and they change on every run, because modern systems place memory at random positions for security. What matters is that &marks and p are the same, and that writing *p = 92 changed marks itself. %p prints an address, and it expects a void * (a generic address), hence the cast. On 64-bit systems every pointer is 8 bytes, whatever type it points to.
The * has two jobs that look alike. In a declaration (int *p), it says "p is a pointer". In an expression (*p = 92), it means "the thing p points to".
Changing the caller's variables
In Functions & Prototypes you saw that functions receive copies of their arguments. To let a function change a variable, pass its address:
#include <stdio.h>
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int priya_seat = 14;
int kabir_seat = 27;
swap(&priya_seat, &kabir_seat);
printf("Priya: %d, Kabir: %d\n", priya_seat, kabir_seat);
return 0;
}Priya: 27, Kabir: 14
swap receives two addresses. The pointers themselves are still copies, but they point at main's variables, so writing through them changes the originals. This also explains scanf("%d", &age): scanf needs to know where to store the number.
Returning several results
A function can return only one value, but output parameters let it hand back more. The caller passes addresses, and the function fills them in:
#include <stdio.h>
void min_max(const int values[], size_t n, int *min, int *max)
{
*min = values[0];
*max = values[0];
for (size_t i = 1; i < n; i++) {
if (values[i] < *min) {
*min = values[i];
}
if (values[i] > *max) {
*max = values[i];
}
}
}
int main(void)
{
int prices[] = {1299, 899, 2499, 1599, 749};
int low, high;
min_max(prices, 5, &low, &high);
printf("Cheapest: Rs %d, costliest: Rs %d\n", low, high);
return 0;
}Cheapest: Rs 749, costliest: Rs 2499
Null pointers: nullptr and NULL
Sometimes a pointer has nothing to point to: a search that found nothing, or a value not yet available. For this C has the null pointer, a special value that never equals the address of any real object. Checking for it is how functions report "not found".
#include <stdio.h>
int *find_first_below(int marks[], size_t n, int limit)
{
for (size_t i = 0; i < n; i++) {
if (marks[i] < limit) {
return &marks[i];
}
}
return nullptr; // nothing found
}
int main(void)
{
int marks[] = {56, 71, 38, 90};
int *failed = find_first_below(marks, 4, 40);
if (failed != nullptr) {
printf("First failing mark: %d\n", *failed);
*failed += 2; // grace marks, written straight into the array
printf("After grace: %d\n", marks[2]);
}
if (find_first_below(marks, 4, 30) == nullptr) {
printf("Nobody scored below 30\n");
}
return 0;
}First failing mark: 38 After grace: 40 Nobody scored below 30
The function returns the address of the matching element, so the caller can read it and even change it. Before you dereference any pointer that might be null, check it. if (p) is a common short form of if (p != nullptr). Dereferencing a null pointer is undefined behaviour; on Linux it usually stops the program with "Segmentation fault".
Pointers and const
const can protect either the value being pointed to or the pointer itself. Read the declaration from right to left:
#include <stdio.h>
int main(void)
{
int stock = 50;
int other = 10;
const int *view = &stock; // pointer to a const int
// *view = 40; // error: assignment of read-only location
view = &other; // allowed: view can point elsewhere
int *const fixed = &stock; // const pointer to an int
*fixed = 45; // allowed: stock becomes 45
// fixed = &other; // error: assignment of read-only variable 'fixed'
printf("stock = %d, *view = %d\n", stock, *view);
return 0;
}stock = 45, *view = 10
const int * is the form you will use most, in function parameters such as const int values[] or const char *name. It promises that the function only reads the data, and the compiler enforces the promise.
Dangerous pointers
Three kinds of pointer cause most crashes:
- Uninitialised (wild) pointers.
int *p; *p = 5;writes to a random address. Always initialise pointers, to a real address or tonullptr. - Dangling pointers. A pointer to a local variable becomes invalid when the function returns, and a pointer to freed memory becomes invalid after
free. Using it is undefined behaviour. - Null dereference. Following
nullptrcrashes. Check before you use a pointer that might be null.
Common mistakes
int *p, q;declares only one pointer.pis a pointer butqis a plainint. Declare one pointer per line.- Forgetting
&or*. Passingageinstead of&age, or printingpwhen you meant*p.-Wallcatches many of these. - Returning the address of a local variable. GCC warns "function returns address of local variable"; return the value instead, or use allocated memory.
- Printing an address with
%d. Use%pand cast tovoid *. - Mixing pointer types. Storing the address of a
doublein anint *is an error in modern GCC.
Key points
- A pointer stores an address;
&xgives the address ofx, and*preaches the valueppoints to. - Pass addresses to let a function change the caller's variables or return several results.
nullptr(C23) is the null pointer; older code usesNULLor0. Check before dereferencing.const int *pprotects the pointed-to value;int *const pprotects the pointer itself.- Never use uninitialised, dangling or null pointers; initialise every pointer when you declare it.
Exercise
Four friends in Pune share a dinner bill of Rs 2,350 equally, in whole rupees. Write a function void split_bill(int total, int people, int *share, int *left_over) that stores each person's share and the rupees left over through its pointer parameters. Call it from main and print both results.
Show solution
#include <stdio.h>
void split_bill(int total, int people, int *share, int *left_over)
{
*share = total / people;
*left_over = total % people;
}
int main(void)
{
int share = 0;
int left_over = 0;
split_bill(2350, 4, &share, &left_over);
printf("Each pays Rs %d, Rs %d left over\n", share, left_over);
return 0;
}Each pays Rs 587, Rs 2 left over
main passes the addresses of share and left_over, so the function can store two results. 4 × 587 = 2348, leaving Rs 2.
Quick Quiz
1. If int x = 5; int *p = &x;, what is *p?
*p follows the address stored in p and gives the value of x.
2. Why does scanf("%d", &n) need the &?
Functions get copies of their arguments; passing the address lets scanf change n.
3. What is the C23 way to write a null pointer?
nullptr is a C23 keyword with its own type; NULL and 0 are older forms.
4. In int *p, q;, what is the type of q?
The * belongs to p only, so q is a plain int.
Learn C with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
