C in 100 Seconds: Walk an Array With a Pointer — Pointer Arithmetic | Episode 18
Video: C in 100 Seconds: Walk an Array With a Pointer — Pointer Arithmetic | Episode 18 by Taught by Celeste AI - AI Coding Coach
C Pointer Arithmetic: Walk an Array With a Pointer
*(ptr + i)is the same asptr[i]. Pointer + int advances bysizeof(*ptr)bytes. Arrays decay to pointers when used in expressions — that's whynums[i]and*(nums + i)work identically.
Pointer arithmetic is C's bridge between addresses and arrays. When you understand how ptr + 1 advances, you understand how nums[i] actually works.
The basic shape
#include <stdio.h>
int main() {
int nums[] = {10, 20, 30, 40, 50};
int *ptr = nums;
printf("ptr points to: %d\n", *ptr); // 10
printf("ptr + 1: %d\n", *(ptr + 1)); // 20
printf("ptr + 3: %d\n", *(ptr + 3)); // 40
for (int i = 0; i < 5; i++) {
printf(" *(ptr + %d) = %d\n", i, *(ptr + i));
}
return 0;
}
Array decay
int nums[] = {10, 20, 30, 40, 50};
int *ptr = nums;
nums is an array, but when you assign it to a pointer or pass it to a function, it decays to a pointer to its first element. So int *ptr = nums is equivalent to int *ptr = &nums[0].
After decay, all the array's "size" information is lost. sizeof(ptr) is 8 (pointer size); sizeof(nums) (in the same scope) is 20 (5 ints × 4 bytes).
Pointer + int
int *p = nums; // points to nums[0]
int *q = p + 1; // points to nums[1]
int *r = p + 3; // points to nums[3]
p + N advances p by N * sizeof(*p) bytes. For int *, that's N * 4 bytes (typical).
The pointer arithmetic is typed. int * and char * advance differently:
int arr[5];
int *ip = arr;
char *cp = (char*)arr;
// ip + 1 advances 4 bytes (one int)
// cp + 1 advances 1 byte (one char)
*(ptr + i) is ptr[i]
int x1 = nums[2]; // array indexing
int x2 = *(nums + 2); // pointer arithmetic
// x1 == x2
a[i] is defined as *(a + i) in the C standard. They're not just equivalent — they're literally the same operation.
A funny consequence: i[a] works too (because a + i == i + a):
int x = 2[nums]; // weird but valid; same as nums[2]
Don't actually write that. But it explains why you sometimes see weird code in obfuscated-C contests.
Pointer subtraction
int *p = nums;
int *q = nums + 4; // points to nums[4]
ptrdiff_t diff = q - p; // 4 (number of ints between them)
q - p returns the number of elements between the pointers (not bytes). Result type is ptrdiff_t from <stddef.h>.
Subtracting pointers into different arrays is undefined behavior — the compiler has no way to relate them.
Comparing pointers
int *start = nums;
int *end = nums + 5;
while (start < end) {
printf("%d ", *start);
start++;
}
Pointers within the same array can be compared with <, <=, >, >=, ==, !=. Useful for "walk from here to there" loops.
++ and --
int *p = nums;
*p++; // dereference, then advance p (post-increment)
*++p; // advance p, then dereference (pre-increment)
*p--; // dereference, then decrement
Increment/decrement work on pointers. For walking arrays:
int *p = nums;
for (int i = 0; i < 5; i++) {
printf("%d ", *p++); // print, then advance
}
The *p++ idiom is so common in C that it's worth recognising on sight.
When pointer arithmetic is undefined
int nums[5];
int *p = nums + 5; // OK: one past the end is allowed
int *q = nums + 10; // UB: too far past the end
int x = *(nums + 5); // UB: reading past the end
C lets you compute "one past the end" pointer (used as a sentinel in loops). Computing further or dereferencing past the end is undefined.
Walking strings
char *s = "hello";
while (*s) {
printf("%c", *s);
s++;
}
// "hello"
A string is a char * — a pointer. *s is the current character. s++ advances. The loop continues while the current char isn't \0 (which is falsy).
This is how strlen, strcpy, etc. are usually implemented internally.
char* vs char[] for strings
char a[] = "hello"; // a is an array of 6 chars; mutable
char *b = "hello"; // b is a pointer to a string literal; read-only
a[0] = 'H'; // OK
b[0] = 'H'; // UB — modifying read-only memory
Episode 11 covered this. The difference shows up here in pointer arithmetic — both a + 1 and b + 1 advance one byte, but writing through b crashes.
Indexing without an array
int *p = malloc(5 * sizeof(int));
p[0] = 10;
p[3] = 40;
*(p + 2) = 30;
After malloc, p is a pointer to enough memory for 5 ints. You can index it with [] even though it's not declared as an array. From C's perspective, "array indexing" is just "pointer arithmetic + dereference."
Common mistakes
Wrong type pointer arithmetic. int *p; p++ advances 4 bytes. (char*)p + 1 advances 1 byte. Mismatch and you read wrong data.
Past-the-end dereferencing. *(nums + 5) on a 5-element array is undefined. Tools like AddressSanitizer catch most.
Comparing unrelated pointers. int a[5], b[5]; a < b; is undefined.
Not advancing the pointer in loops. while (*p) { ...; } (no p++) is infinite — pointer never moves.
Pointer arithmetic on void *. Doesn't compile in standard C — void has no size. Cast to char * first.
Why arrays decay (briefly)
Historically, C didn't pass arrays by value (no automatic copying). The convention became: passing an array means "passing a pointer to its first element." This decay is automatic — func(nums) is func(&nums[0]).
Inside the function, you've lost the array's size — that's why you always need to pass it as a separate parameter.
What's next
Episode 19: pass by reference. Functions that modify the caller's variables, via pointer parameters.
Recap
Pointer + int advances by sizeof(*ptr) bytes — typed arithmetic. *(p + i) and p[i] are the same thing. Arrays decay to pointers when used in expressions — nums becomes &nums[0]. Pointer subtraction returns element count. Comparisons work between pointers in the same array. *p++ is the idiomatic "read and advance." One-past-the-end pointers are valid; dereferencing them is not.
Next episode: pass by reference.