Lesson 107 min read

C Pointer Arithmetic & Arrays

Arrays and pointers are best friends in C β€” almost the same person wearing different hats

Two Friends, One Address

Here's one of C's most mind-bending facts: an array name is secretly a pointer to its first element. When you write int arr[5];, the name arr is essentially &arr[0] β€” the address of locker 0.

This means you can use pointer syntax on arrays and array syntax on pointers. They're interchangeable in most contexts. The expression arr[i] is literally rewritten by the compiler as *(arr + i).

But they're not identical. An array name is a constant β€” you can't make it point somewhere else. A pointer variable can be reassigned. Think of the array name as a permanent sign bolted to locker 0, while a pointer is a movable sticky note.

Array Name as Pointer

#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // same as &arr[0]
printf("arr[0] = %d\n", arr[0]);
printf("*ptr = %d\n", *ptr);
printf("*(arr+2) = %d\n", *(arr + 2));
printf("ptr[2] = %d\n", ptr[2]);
// They point to the same place
printf("\narr = %p\n", (void*)arr);
printf("ptr = %p\n", (void*)ptr);
printf("&arr[0] = %p\n", (void*)&arr[0]);
return 0;
}
Output
arr[0] = 10
*ptr   = 10
*(arr+2) = 30
ptr[2]   = 30

arr  = 0x7ffd3a2b1040
ptr  = 0x7ffd3a2b1040
&arr[0] = 0x7ffd3a2b1040

How Pointer Arithmetic Works

When you add 1 to a pointer, it doesn't move forward 1 byte. It moves forward by the size of the type it points to. An int* moves 4 bytes, a double* moves 8 bytes, a char* moves 1 byte.

This is what makes pointer arithmetic so elegant β€” you think in elements, not bytes. ptr + 3 means "3 elements ahead," regardless of how many bytes that actually is.

You can also subtract two pointers of the same type. The result is the number of elements between them β€” again, not bytes.

Pointer Arithmetic Steps by Type Size

#include <stdio.h>
int main() {
int iArr[] = {10, 20, 30};
char cArr[] = {'A', 'B', 'C'};
double dArr[] = {1.1, 2.2, 3.3};
int *ip = iArr;
char *cp = cArr;
double *dp = dArr;
printf("=== int* (4 bytes per step) ===\n");
printf("ip = %p -> %d\n", (void*)ip, *ip);
printf("ip + 1 = %p -> %d\n", (void*)(ip+1), *(ip+1));
printf("ip + 2 = %p -> %d\n", (void*)(ip+2), *(ip+2));
printf("\n=== char* (1 byte per step) ===\n");
printf("cp = %p -> %c\n", (void*)cp, *cp);
printf("cp + 1 = %p -> %c\n", (void*)(cp+1), *(cp+1));
printf("\n=== double* (8 bytes per step) ===\n");
printf("dp = %p -> %.1f\n", (void*)dp, *dp);
printf("dp + 1 = %p -> %.1f\n", (void*)(dp+1), *(dp+1));
// Pointer subtraction
printf("\nElements between ip+2 and ip: %ld\n",
(ip + 2) - ip);
return 0;
}
Output
=== int* (4 bytes per step) ===
ip     = 0x7ffd1a2b0040  -> 10
ip + 1 = 0x7ffd1a2b0044  -> 20
ip + 2 = 0x7ffd1a2b0048  -> 30

=== char* (1 byte per step) ===
cp     = 0x7ffd1a2b0030  -> A
cp + 1 = 0x7ffd1a2b0031  -> B

=== double* (8 bytes per step) ===
dp     = 0x7ffd1a2b0050  -> 1.1
dp + 1 = 0x7ffd1a2b0058  -> 2.2

Elements between ip+2 and ip: 2
Note: Pointer arithmetic steps by sizeof(type), not by 1 byte. When you do int *p; p + 1;, the address increases by 4 (the size of an int), not by 1. This is the most common misconception. If you cast to char* and add 1, then it moves by 1 byte β€” because sizeof(char) is 1.

Traversing an Array with a Pointer

Instead of using an index variable, you can walk a pointer through an array. Start at the beginning, increment with ptr++, and stop when you reach the end. This is how many C standard library functions work internally.

Traversing an Array with a Pointer

#include <stdio.h>
void printArray(int *arr, int size) {
int *end = arr + size; // one past the last element
printf("[ ");
for (int *p = arr; p < end; p++) {
printf("%d ", *p);
}
printf("]\n");
}
int sumArray(int *arr, int size) {
int total = 0;
for (int *p = arr; p < arr + size; p++) {
total += *p;
}
return total;
}
int main() {
int data[] = {4, 8, 15, 16, 23, 42};
int len = sizeof(data) / sizeof(data[0]);
printArray(data, len);
printf("Sum = %d\n", sumArray(data, len));
// You can also pass a slice!
printf("Middle 3: ");
printArray(data + 1, 3);
return 0;
}
Output
[ 4 8 15 16 23 42 ]
Sum = 108
Middle 3: [ 8 15 16 ]

The Equivalence: arr[i] == *(arr + i)

This is the fundamental identity of C arrays and pointers. The compiler translates every arr[i] into *(arr + i). Because addition is commutative, this means arr[i] is also *(i + arr), which means β€” believe it or not β€” i[arr] is valid C. (Please never write that in real code.)

Array of Strings

A common pattern in C is an array of pointers to characters β€” essentially an array of strings. Each element is a char* pointing to a string literal. This is exactly what argv is in int main(int argc, char *argv[]).

Array of Strings (char*[])

#include <stdio.h>
int main() {
// Array of pointers to string literals
const char *colors[] = {
"red",
"green",
"blue",
"yellow"
};
int count = sizeof(colors) / sizeof(colors[0]);
for (int i = 0; i < count; i++) {
printf("Color %d: %s\n", i, colors[i]);
}
// Each element is a pointer
printf("\nFirst char of colors[1]: %c\n",
colors[1][0]); // 'g'
printf("Same thing with pointers: %c\n",
*(colors[1])); // also 'g'
// Fun fact: arr[i] == *(arr+i) == *(i+arr) == i[arr]
printf("\n2[colors] = %s (please don't do this)\n",
2[colors]);
return 0;
}
Output
Color 0: red
Color 1: green
Color 2: blue
Color 3: yellow

First char of colors[1]: g
Same thing with pointers: g

2[colors] = blue (please don't do this)

Pointer to Array vs. Array of Pointers

The syntax can be confusing:

  • int *arr[5] β€” an array of 5 pointers to int (5 sticky notes)
  • int (*arr)[5] β€” a pointer to an array of 5 ints (1 sticky note pointing to a row of 5 lockers)

The parentheses make all the difference. When in doubt, read declarations from the variable name outward: arr is a *(pointer) to [5](array of 5) int(integers).

Challenge

Quick check

If int *p points to address 0x1000, what is the address at p + 3?
← PointersDynamic Memory β†’