Arrays and pointers are different things in C that behave the same way most of the time. That "most of the time" is what makes the topic slippery: code that treats an array as a pointer usually works, until the day it lands in one of the two places where the difference matters and the answer is silently wrong.
This page draws the line precisely.
Array Decay: The One Rule Behind Everything
In almost every expression, an array name is automatically converted to a pointer to its first element. This is called decay. arr becomes &arr[0], with type int *.
The array name and the address of its first element print identically. That is the decay in action, and it is why you can assign an array to a pointer with no &.
There are exactly three places decay does not happen: as the operand of sizeof, as the operand of &, and when initializing a character array from a string literal. Everywhere else, assume decay.
arr[i] Is Literally *(arr + i)
The C standard defines the subscript operator in terms of pointer arithmetic: a[b] means *(a + b). Indexing is not a separate feature - it is shorthand.
Two consequences fall out. First, a pointer can be subscripted: p[2] is *(p + 2), which is why functions receiving a pointer can still use familiar arr[i] syntax. Second, 2[arr] compiles - *(2 + arr) is the same address as *(arr + 2). Nobody writes that outside of a quiz, but it settles the question of whether indexing is "really" pointer arithmetic.
Passing an Array to a Function
Because of decay, a function never receives an array. It receives a pointer.
Three things worth pulling out of that example.
The size in int arr[100] is documentation, nothing more. The compiler rewrites the parameter to int *arr and never checks that you passed 100 of anything.
The length must travel separately. The function has an address and no idea how far the data extends. This is why virtually every array-taking C function takes a count: memcpy(dst, src, n), fread(ptr, size, count, f), qsort(base, nmemb, size, cmp). Strings are the exception only because they carry their own end marker, the '\0'.
Passing a pointer is cheap and the data is shared. No copy of the array is made, so the function can modify the caller's elements - that is often exactly what you want:
Mark read-only parameters const int * as print_all does. It documents the intent and lets the compiler catch an accidental write.
The sizeof Trap
Here is the bug that catches every C programmer once. sizeof is one of the two places decay does not happen - so it gives the true array size where the array is declared, and the size of a pointer anywhere the array has decayed.
In main, sizeof data is 40 bytes and the length computes to 10. Inside inspect, sizeof arr is the size of a pointer - 8 on a 64-bit machine - and the "length" comes out as 2. The code looks identical and is quietly wrong.
The rule that follows: compute an array's length only in the scope where the array was declared, and pass it along from there.
& of an Array Is Not the Same Type
The other place decay does not happen is the & operator. &arr is a pointer to the whole array, not to its first element. Both hold the same address; the difference is what "add 1" means.
Same starting address, different stride. arr has type int * and steps by one element; &arr has type int (*)[5] and steps by the entire array. This is the mechanism that makes passing a row of a 2D array work, which is covered in multidimensional arrays.
Pointer to Array vs Array of Pointers
Two declarations that look alike and mean opposite things:
int *p[5]; // array of 5 pointers to int
int (*q)[5]; // pointer to an array of 5 ints
[] has higher precedence than *, so int *p[5] parses as "p is an array, of pointers". The parentheses in int (*q)[5] force * to bind first: "q is a pointer, to an array".
int *p[5] int (*q)[5]
p[0] -> [ int ] q -> [ int | int | int | int | int ]
p[1] -> [ int ] one contiguous block of 5
p[2] -> [ int ]
p[3] -> [ int ]
p[4] -> [ int ]
five separate addresses
An array of pointers is the common one, and you have already used it: char *argv[] is an array of pointers to strings, one per command-line argument. It is also how you hold a list of strings of different lengths without wasting space.
Note (*q)[0]: dereference the pointer to get the array, then index it. The parentheses are required again, for the same precedence reason.
Where Arrays and Pointers Genuinely Differ
Keep this short table in mind and the confusion disappears:
Array int arr[10] | Pointer int *p | |
|---|---|---|
| What it is | 10 ints laid out in memory | one variable holding an address |
sizeof | 40 - the whole block | 8 - just the pointer |
| Assignable | no: arr = x; is an error | yes: p = x; is fine |
& gives | int (*)[10] | int ** |
| Where memory comes from | declared with the array | wherever you point it |
An array name is not a variable holding an address - it is the storage, and the address is computed on demand. That is why you cannot assign to it.
Frequently Asked Questions
Are arrays and pointers the same thing in C?
No, but they are easy to confuse because an array name decays into a pointer to its first element in almost every expression. The array itself is a block of elements with a known size; a pointer is a single variable holding one address. sizeof and & are the two places the difference shows.
Why does sizeof(arr) give the wrong answer inside a function?
Because the parameter is not an array. void f(int arr[]) is silently rewritten to void f(int *arr), so sizeof(arr) measures a pointer (8 bytes on most machines), not the original array. Pass the length as a separate parameter.
What is the difference between int *p[5] and int (*p)[5]?
int *p[5] is an array of 5 pointers to int. int (*p)[5] is a single pointer to an array of 5 ints. The parentheses bind the * to the name first; without them [] wins because it has higher precedence.
Is arr[i] really the same as *(arr + i)?
Yes - the standard defines a[b] as *(a + b). That is also why the strange-looking i[arr] compiles and works: addition commutes, so *(i + arr) is the same element. Never write it in real code, but it proves the rule.