Pointers · Lesson 22 of 35
Pointers in C Explained
Pointers are what make C powerful – and what scare most learners. The idea is simple: every variable lives at a memory address, and a pointer is a variable that stores an address. Through a pointer, a function can change the caller's variables, walk through arrays, and manage memory directly. Master this lesson and the rest of C opens up.
In this lesson
- Addresses and the
&operator - Declaring pointers and dereferencing with
* - NULL and uninitialised pointers
- Call by reference: functions that change your variables
Addresses
Memory is a long row of numbered bytes. When you declare int marks = 85;, the compiler reserves 4 bytes at some address – say 2000 – and stores 85 there. &marks ("address of marks") gives that address.
A variable and a pointer to it
marks85at address 2000p2000an int pointer, at address 3000 – it "points to" marks*p85the value at the address p holds
Declaring and using a pointer
#include <stdio.h>
int main(void) {
int marks = 85;
int *p = &marks; // p is a "pointer to int" holding marks's address
printf("marks = %d\n", marks);
printf("*p = %d\n", *p); // dereference: the value p points to
printf("p == &marks ? %s\n", p == &marks ? "yes" : "no");
*p = 92; // writing through the pointer changes marks
printf("marks now = %d\n", marks);
int bonus = 5;
p = &bonus; // a pointer can be re-pointed
*p += 10;
printf("bonus now = %d, marks still %d\n", bonus, marks);
printf("a pointer takes %zu bytes here\n", sizeof p);
return 0;
}
marks = 85 *p = 85 p == &marks ? yes marks now = 92 bonus now = 15, marks still 92 a pointer takes 8 bytes here
| Syntax | Read it as |
|---|---|
int *p; | p is a pointer to an int |
p = &x; | p gets the address of x |
*p | the int that p points to (dereference) |
*p = 7; | store 7 in the int that p points to |
The * means two different things: in a declaration (int *p) it says "p is a pointer"; in an expression (*p) it means "follow the pointer". To print an address, use printf("%p", (void *)p) – the value changes every time the program runs.
Declaring several pointers
int *a, b; makes a a pointer but b a plain int – the * belongs to each name. Write int *a, *b; or declare them on separate lines.
NULL and wild pointers
#include <stdio.h>
int find_index(const int a[], int n, int key) {
for (int i = 0; i < n; i++) if (a[i] == key) return i;
return -1;
}
int main(void) {
int *p = NULL; // "points nowhere" – safe to test
if (p == NULL) printf("p is not pointing at anything yet\n");
int data[] = {4, 8, 15};
int value = 8;
int i = find_index(data, 3, value);
p = (i >= 0) ? &data[i] : NULL;
if (p != NULL) printf("found %d at index %d\n", *p, i);
return 0;
}
p is not pointing at anything yet found 8 at index 1
Three dangerous pointers
- Wild pointer: declared but never initialised (
int *p; *p = 5;) – writes to a random address. - NULL dereference:
*pwhen p is NULL – a guaranteed crash ("segmentation fault"). Check before using. - Dangling pointer: points to memory that no longer exists (a returned local, freed memory).
Initialise every pointer – to a real address or to NULL.
Call by reference
In chapter 5, swap_wrong couldn't change the caller's variables because it received copies. Pass addresses instead, and the function can reach the originals:
#include <stdio.h>
void swap(int *a, int *b) {
int t = *a; // value at address a
*a = *b;
*b = t;
}
void min_max(const int arr[], int n, int *min, int *max) { // "return" two values
*min = *max = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] < *min) *min = arr[i];
if (arr[i] > *max) *max = arr[i];
}
}
int main(void) {
int x = 10, y = 20;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y);
int marks[] = {67, 92, 45, 78};
int lo, hi;
min_max(marks, 4, &lo, &hi);
printf("min = %d, max = %d\n", lo, hi);
return 0;
}
x = 20, y = 10 min = 45, max = 92
swap(&x, &y) step by step
- a holds &x, b holds &ycopies of the addresses
- t = *at = 10 (the value of x)
- *a = *bx becomes 20
- *b = ty becomes 10
This is exactly how scanf("%d", &n) stores a value into n, and how functions return several results.
Practice
Try it yourself
Write
void divide(int a, int b, int *quotient, int *remainder)and use it on 47 and 5.Show solution
#include <stdio.h> void divide(int a, int b, int *quotient, int *remainder) { *quotient = a / b; *remainder = a % b; } int main(void) { int q, r; divide(47, 5, &q, &r); printf("47 = 5 x %d + %d\n", q, r); return 0; }47 = 5 x 9 + 2
Quick check
If int x = 4; int *p = &x;, what is *p?
4.
What does *p = 9; do?
Stores 9 in the variable p points to (x becomes 9).
How can a function change two of the caller's variables?
Receive their addresses as pointer parameters and write through them.
Summary
- A pointer stores an address; &x gives x's address and *p follows a pointer.
- Initialise pointers to a valid address or NULL; never dereference NULL or dangling pointers.
- Passing addresses (call by reference) lets functions modify the caller's variables.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.