Part of C in 100s

C in 100 Seconds: Walk an Array With a Pointer — Pointer Arithmetic | Episode 18

Celest KimCelest Kim

Video: C in 100 Seconds: Walk an Array With a Pointer — Pointer Arithmetic | Episode 18 by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

C Pointer Arithmetic: Walk an Array With a Pointer

*(ptr + i) is the same as ptr[i]. Pointer + int advances by sizeof(*ptr) bytes. Arrays decay to pointers when used in expressions — that's why nums[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.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.