It is the most powerful tool in the C language, and at the same time, it is the cause of the most bugs. Pointers deal not with “values” but with “where the value is (address)”.
Learning Objectives
- Understand the declaration of pointer variables and the
&and*operators. - Modify the caller's variables by passing a pointer to the function.
- We know that array names actually act like pointers.
- Learn pointer arithmetic and NULL check patterns.
Why do we need pointers? — "Room number of mailbox"
Just as you can send mail without having to go to the house in person, you just need to know “room number”. A pointer is a means of indirect access to a value in memory.
The world of variables The world of pointers
Deals with the value itself Deals with the address where the value is located
“One apple” “Second compartment of the refrigerator”
This indirect approach makes it possible to:
- Function modifies the caller’s variable (imitating call by reference)
- Pass only the address without copying large data (performance)
- Use dynamic memory (runtime size determination, next section)
- Linked data structure (linked list, tree)
Key concepts
1) Address and backreference
int x = 42;
int *p = &x; //p is the address of x
*p = 100; //Assign 100 to where p points → x = 100
Memory picture:
x (value 42) p (pointer, holds the address of x)
┌────────┐ ┌────────┐
│ 42 │ ◄────────────────── │ &x │
└────────┘ └────────┘
Address: 0x100 Address: 0x200
*p (dereference) = "value pointed to by p" = x.
2) The size of the pointer is independent of the data type.
sizeof(int *) == 8 // 64-bit
sizeof(char *) == 8
sizeof(double *) == 8
A pointer only contains address, so it has the same size no matter what data type it points to.
However, the data type determines how many bytes *p will read from memory and how to interpret them.
3) Arrays and pointers
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; //arr is the address of the first element
p[2]; // 30
*(p + 2); //30 (equivalent)
Array names are converted to a pointer to the first element within the expression.
However, sizeof(arr) is the entire size of the array, and sizeof(p) is the pointer size.
4) NULL pointer
int *p = NULL;
if (p != NULL) *p = 0; //Always check
Dereferencing an invalid pointer will almost certainly cause a segmentation fault. Use NULL to specify "there is no place to point yet".
See as example
Example 1 — ex01_basic.c: Address and dereference
int x = 42;
int *p = &x;
*p = 100;
Execution result (address varies depending on execution)
Value of x: 42
Address of x: 0x7fffda2960a4
Value of p: 0x7fffda2960a4 (= address of x)
*p: 42 (value pointed to by p)
*p = 100 then x = 100
Key: The address where the value of p and &x are the same. *p = 100 actually replaces x.
Example 2 — ex02_swap.c: Exchange two values with a function
void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; }
swap(&x, &y);
Execution result
Before exchange: x=1, y=2
After exchange: x=2, y=1
Bottom line: swap doesn't work if you're passing by value. You must pass the address so that the function can directly modify the caller's variables.
Example 3 — ex03_array_ptr.c: Arrays are in fact pointers
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
Execution result
arr[0]=10, *(p+0)=10, p[0]=10
arr[1]=20, *(p+1)=20, p[1]=20
arr[2]=30, *(p+2)=30, p[2]=30
arr[3]=40, *(p+3)=40, p[3]=40
arr[4]=50, *(p+4)=50, p[4]=50
sizeof(arr) = 20
sizeof(p) = 8
Bottom line: arr[i], *(p+i), p[i] are all the same. But sizeof is different.
Example 4 — ex04_ptr_arith.c: Pointer arithmetic
int arr[] = {100, 200, 300, 400};
int *p = arr;
p++; //Increase by sizeof(int)
p += 2;
Execution result (address example)
p -> 100 (address 0x7ffee6ef7c40)
p+1 -> 200 (address 0x7ffee6ef7c44)
p+3 -> 400 (address 0x7ffee6ef7c4c)
last - first = 3
Key point: Pointer arithmetic moves in data type size units (4 bytes for int).
The result of last - first is difference in number of elements.
A different perspective — variables vs pointers
Using variables only: Using pointers:
┌──────┐ ┌──────┐
│ x=10 │ Function -- (Copy value 10) ─► │ x=10 │ Function -- (Pass address 0x100) ─►
└──────┘ Only copy within the function └──────┘ The function is
Can be replaced directly modify the original
A pointer gives a function the "right to borrow and use the original variable".
Common Mistakes
- NULL dereference:
int *p = NULL; *p = 0;→ segfault. - Uninitialized pointer:
int *p; *p = 0;→ Random memory corruption (very dangerous). - Return address of local variable: When the function ends, the address is a dangling pointer.
int *bad(void) { int x = 10; return &x; } /*danger*/ sizeofmisunderstanding: If you writesizeoffor a pointer, it is the size of the pointer itself, not the size of the array it points to.&operator missing: Same mistake asscanf("%d", n).
Cleanup
- A pointer is a “variable that holds an address”. Obtain the address with
&and read and write the value with*. - If a function wants to change a variable in the caller, it must pass the address.
- Array names act like a pointer to the first element in an expression.
- Pointer arithmetic is performed in data type size units.
- NULL checking and initialization are the first line of defense against memory accidents.
Try it yourself
cd src
gcc -std=c11 -Wall -o ex01 ex01_basic.c && ./ex01
gcc -std=c11 -Wall -o ex02 ex02_swap.c && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_array_ptr.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_ptr_arith.c && ./ex04
Application:
- Try what happens when the
swapfunction ofex02receives onlyint a, int bparameters. - In
ex03, create a functionint sum(int *a, int n)that calculates the sum by passingarrto the function instead ofp.