student@ubuntu:~$
pointers-memory Lesson 8 10 min read

Pointer Arithmetic & Arrays

p+1 scales by sizeof(*p), and arr[i] is just *(arr+i)

Reading: Hanly & Koffman: §7.5 (pp. 390–403)

Quick check before you start: Can you declare a pointer, assign it with &, and dereference it with *? If not, review Pointer Basics first.

Practice this topic: Pointer Arithmetic skill drill

After this lesson, you will be able to:

  • Calculate the result of adding an integer to a pointer, accounting for type-size scaling
  • Explain the equivalence between arr[i] and *(arr + i)
  • Traverse an array using pointer increment instead of index-based access
  • Explain why an array parameter decays to a pointer inside a function
  • Distinguish between sizeof on an array vs. sizeof on a pointer

How Pointer Math Works

When you add 1 to a pointer, it does not move by 1 byte – it moves by sizeof the pointed-to type:

int arr[] = {10, 20, 30, 40, 50};
int *p = arr;          /* Points to arr[0] */

printf("%d\n", *p);         /* 10  (arr[0]) */
printf("%d\n", *(p + 1));   /* 20  (arr[1]) */
printf("%d\n", *(p + 2));   /* 30  (arr[2]) */

Since int is 4 bytes, p + 1 moves 4 bytes forward – exactly to the next int:

Memory:  [10] [20] [30] [40] [50]
Address: 1000 1004 1008 1012 1016
          ^    ^    ^
          p    p+1  p+2

Key Insight: p + n moves n * sizeof(*p) bytes forward, not n bytes. For int *p, p + 1 advances 4 bytes. For double *q, q + 1 advances 8 bytes. C scales automatically so you never have to think about byte sizes when walking through arrays.

The Array-Pointer Equivalence

These are interchangeable:

Array notation Pointer notation Meaning
arr[0] *arr First element
arr[i] *(arr + i) Element at index i
&arr[i] arr + i Address of element i

The name of an array, when used in an expression, decays to a pointer to its first element:

int arr[] = {10, 20, 30};
int *p = arr;             /* No & needed -- arr decays to pointer */

Walking an Array with Pointers

Instead of indexing, you can increment a pointer:

int arr[] = {10, 20, 30, 40, 50};
int size = 5;

/* Index-based (familiar): */
for (int i = 0; i < size; i++)
{
    printf("%d ", arr[i]);
}

/* Pointer-based (equivalent): */
int *p = arr;
for (int i = 0; i < size; i++)
{
    printf("%d ", *p);
    p++;                   /* Move to next element */
}

Both print: 10 20 30 40 50

Pointer Subtraction

Subtracting two pointers gives the number of elements between them (not bytes):

int arr[] = {10, 20, 30, 40, 50};
int *start = &arr[0];
int *end = &arr[4];

printf("Elements between: %ld\n", end - start);    /* 4 */

Key Insight: Pointer subtraction is the inverse of pointer addition – both scale by sizeof the type. If p and q are both int * pointers 16 bytes apart, q - p returns 4 (elements), not 16 (bytes). This only works when both pointers point into the same array.

sizeof Array vs. sizeof Pointer

This is why functions cannot use sizeof on array parameters:

void print_size(int arr[])
{
    /* sizeof(arr) is the size of a POINTER (8 bytes), not the array! */
    printf("Inside function: %zu\n", sizeof(arr));    /* 8 */
}

int main(void)
{
    int nums[100];
    printf("In main: %zu\n", sizeof(nums));           /* 400 (100 * 4) */
    print_size(nums);
    return 0;
}

Key Insight: When you pass an array to a function, it decays to a pointer. The function receives int *arr, not the full array. That is why you must always pass the size as a separate parameter – the function has no way to determine it from the pointer alone.

From Java: In Java, arrays are objects with a .length field that travels with the array. In C, the array name decays to a raw pointer when passed to a function – all size information is lost. This is the root of why C requires you to pass size everywhere.


Check Your Understanding
If int *p points to address 0x1000, what address does p + 3 point to? (Assume sizeof(int) is 4.)
A0x1003 -- adds 3 bytes
B0x100C -- adds 3 * 4 = 12 bytes
C0x1004 -- adds 4 bytes (one int)
D0x1030 -- adds 30 bytes
Answer: B. Pointer arithmetic scales by sizeof(int) = 4 bytes. So p + 3 advances 3 * 4 = 12 bytes. 0x1000 + 12 = 0x1000 + 0xC = 0x100C. Option A ignores type scaling. Option C only advances by one element. Option D misreads the hex math.

What Comes Next

Array notation (arr[i]) and pointer notation (*(arr + i)) are the same thing. Understanding this connection means you will never be confused by C code that mixes them.

Next: double pointers. What happens when a function needs to modify a pointer itself, not just the value it points to? You need a pointer to a pointer – int **pp.