Dataslope logoDataslope

Arrays and Pointer Arithmetic

How arrays really work in C, array decay, pointer arithmetic, and passing arrays to functions

C arrays are the most direct expression of "contiguous bytes in memory with a name." They are also surprisingly subtle: an array is not quite a pointer, but it becomes one almost any time you use it.

A C array is contiguous memory

int nums[5] = {10, 20, 30, 40, 50}; reserves enough room for five ints, side by side, and names the start nums.

Code Block
C 17 (201710L)

The addresses should grow by exactly sizeof(int) per element (4 on WASI). That is what we mean by "contiguous": no gaps, no indirection, no per-element header.

a[i] is *(a + i)

Indexing in C is sugar for pointer arithmetic. The expression a[i] is defined as *(a + i). The + i part is typed — it advances by i * sizeof(*a) bytes, not by i bytes.

Code Block
C 17 (201710L)

Yes, 2[a] is valid C: by definition i[a] == *(i + a) == *(a + i) == a[i]. Do not write that in real code — but knowing it explains why arrays and pointers feel so interchangeable.

Array decay

Almost every time you use an array (pass it to a function, assign it to a pointer, use it in arithmetic), the expression of array type is implicitly converted to a pointer to its first element. This is called array-to-pointer decay.

Code Block
C 17 (201710L)

Inside a function, int a[] is exactly the same as int *a. The size information is lost. That is why C functions that take arrays almost always also take a length parameter.

Always pass the length

There is no portable, automatic way to ask a T * "how many elements do you point at?" — you have to track it yourself. This is the single most important convention to internalize in C.

Pointer arithmetic in detail

If p is a pointer to a T, then:

ExpressionMeaning
p + naddress of element n past p (n × sizeof(T) bytes ahead)
p - naddress of element n before p
p - qnumber of elements between two pointers into the same array
*p++dereference p, then advance p
*++padvance p, then dereference
p[n]shorthand for *(p + n)
Code Block
C 17 (201710L)

Pointers may only be compared within the same allocation

Subtracting or comparing pointers that point into different arrays is undefined behavior. The "one-past-the-end" pointer (a + 5 for an array of length 5) is a legal sentinel but must not be dereferenced.

Functions that take arrays

The classic signature is void f(T *arr, size_t n). Inside the function, treat arr and n as inseparable.

Code Block
C 17 (201710L)

The idiom sizeof(array) / sizeof(array[0]) works only where the array's type is in scope — that is, where it has not yet decayed. Hence we compute n in main and pass it down, never inside sum.

2D arrays vs arrays of pointers

Two ways to represent a matrix. They look similar; they are not the same in memory.

int grid[3][4];           // 12 ints in one contiguous block
int *rows[3];             // 3 pointers, each pointing at its own row

The first is one allocation of 3 * 4 * sizeof(int) = 48 bytes laid out in row-major order. The second is three separate allocations (plus an array of three pointers) — flexible but with poorer cache behavior.

Code Block
C 17 (201710L)

Row-major layout means grid[r][c] lives at offset (r * 4 + c) * sizeof(int) from grid. Iterating in row-major order (outer loop over rows, inner loop over columns) walks memory linearly and is cache-friendly.

Bounds checking — there isn't any

C arrays do not check bounds. Writing a[100] for a 10-element array will quietly stomp on whatever memory follows the array — a heap metadata structure, another variable, the return address on the stack. That is the C bug.

Code Block
C 17 (201710L)

On the WASI target you may see sentinel change to 42, stay at 999, or get something else entirely. We will return to bugs like this in the Memory Bugs chapter.

Practice: rotate an array left by one

Challenge
C 17 (201710L)
Rotate left in-place

Implement void rotate_left(int *arr, size_t n) so that the element at index 0 moves to the end and every other element shifts one position to the left. For {1,2,3,4,5} the result is {2,3,4,5,1}. The provided main prints the array space-separated on one line.

Practice: matrix diagonal sum

Challenge
C 17 (201710L)
Sum the main diagonal of a 4x4 matrix

Implement int diag_sum(int m[4][4]) that returns m[0][0] + m[1][1] + m[2][2] + m[3][3]. The provided main defines a 4x4 matrix and prints the result followed by a newline.

Test your understanding

QuestionSelect one

Inside void f(int a[], size_t n), what does sizeof(a) give you?

The total number of bytes in the original array.

The size of a pointer (because a has decayed to int *).

The number of elements in the array.

Always zero.

QuestionSelect one

Given int a[10]; and int *p = a + 3;, what is p - a?

3 bytes

3 * sizeof(int)

3 elements

Undefined behavior

QuestionSelect one

What happens if you write past the end of a C array, e.g. a[100] = 0 for an array of length 10?

The compiler refuses to build the program.

The runtime throws an ArrayIndexOutOfBoundsException.

The write silently fails and leaves memory unchanged.

It is undefined behavior — it may crash, may overwrite unrelated data, or may appear to work.

On this page