Pointers and Arrays
Why an array name "decays" into a pointer, and how pointer arithmetic walks through memory
In C, arrays and pointers are tangled together in a way that shocks newcomers and trips up old hands. The shock is the price of admission. The payoff is a unified model in which traversing an array, walking a string, and scanning a region of memory are all the same idea.
Array name as a pointer
When you mention an array name in most expressions, the compiler silently converts it to a pointer to its first element. This is called array-to-pointer decay.
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // same as &a[0]p and a point to the same byte. The differences are subtle but
real:
sizeof(a)is5 * sizeof(int)— the total array size.sizeof(p)is the size of a pointer, typically 8.a = somewhere_else;— forbidden, an array name can't be reassigned.p = somewhere_else;— fine, pointers are just variables.
a[i], p[i], and *(p + i) are all the same thing. The C
standard defines indexing in terms of pointer arithmetic.
Pointer arithmetic
When you add 1 to a pointer, the compiler does not add 1 byte —
it adds the size of one element of the pointed-to type. So p + 1
on an int * advances 4 bytes (on a typical system), landing on
the next int.
This is why arrays and pointers fit so neatly together. Walking an array with a pointer is just incrementing the pointer.
Both print 10 20 30 40 50. Choose whichever style makes the
intent clearest. Idiomatic C often uses the pointer form for strings
and the index form for numeric arrays — but neither is "more
correct".
The classic string-copy loop
Reading a null-terminated string with a pointer is the most famous pointer-arithmetic loop in C:
Each iteration:
- Read the character at the pointer (
*s). - If it's
\0, stop. - Otherwise print it.
- Advance the pointer to the next character.
The famous one-liner while (*s) putchar(*s++); does the same thing
even more compactly. Familiarize yourself with the long form first
— compactness is a reward you can earn later.
Arrays as function parameters: revisited
Recall that when you pass an array to a function, the array decays to a pointer:
void show(int arr[], int n); // these two declarations
void show(int *arr, int n); // are completely equivalentInside show, arr is a int *, and sizeof(arr) does not
give you the array's size — it gives the size of a pointer. The
length must always be passed explicitly. This is why you see
length-carrying APIs everywhere in C:
size_t strlen(const char *s);
void *memcpy(void *dest, const void *src, size_t n);Idiomatic C functions take a pointer to the first element and a size. Always.
Pointer subtraction
Subtracting two pointers into the same array gives the distance between them, in elements:
comma - s is 5 — the offset of , from the start of the string.
Pointer subtraction is one of the cleanest ways to compute lengths
and positions.
Walking a 2D array with pointers
A 2D array int grid[ROWS][COLS] lives in memory as one long row of
ints, row by row (row-major order). You can walk all ROWS * COLS
elements with a single pointer:
Output: sum = 78. The 2D shape is a convenient indexing scheme
on top of a flat region of memory.
Pointer pitfalls
Three rules that, if you follow them, dodge most pointer bugs:
- Don't point at things that have died. Local variables evaporate when their function returns. Returning a pointer to a local is a guaranteed bug.
- Don't step outside an array. Pointer arithmetic that wanders past the start or end of an array is undefined behavior.
- Set unused pointers to
NULLand check before dereferencing.
int *make_a_bug(void) {
int x = 5;
return &x; // BUG: x dies as this function returns
}Most compilers will warn about this. Take the warning seriously.
Visual: pointer arithmetic on an int array
Each cell is 4 bytes apart, but p + 1 adds 1 element — the
arithmetic accounts for sizeof(int) for you.
Challenge: sum an array via a pointer
Write a function int sum(const int *begin, const int *end) that adds together every element from begin (inclusive) up to end (exclusive). main calls it like this:
int a[] = {1, 2, 3, 4, 5};
int total = sum(a, a + 5);
printf("%d\n", total); // should print 15
The program must print exactly 15.
If int a[5]; and int *p = a;, which of the following is not equivalent to a[3]?
Hint: a[3] is a value. Which option gives you an address instead?
p[3]
*(p + 3)
*(a + 3)
a + 3
Why does this function return a meaningless number for length?
void show(int arr[]) {
int length = sizeof(arr) / sizeof(arr[0]);
printf("%d\n", length);
}
The compiler can't compute sizeof at runtime.
The function should declare arr as int *arr instead.
Inside the function, arr is a pointer, not an array. sizeof(arr) is the size of a pointer (typically 8), not the array's total size.
C functions cannot compute the length of an array under any circumstances.