Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Array Name vs Pointer
Learn why arr++ fails, what &arr really means and how negative subscripts work. Free C programming course for embedded systems.
What You Will Learn in This Lecture
- Why the array name in C is not a pointer variable, even though it behaves like one in most expressions
- Why
arr++is a compile error whilep++is fine - What
&arrmeans and why&arr + 1jumps further thanarr + 1 - How negative subscripts such as
p[-1]work on a pointer - How
(arr + 1)[2]is evaluated and why it equalsarr[3] - The array-versus-pointer rules that matter in embedded C programming
Prerequisites
Complete the earlier lectures on pointer arithmetic and on pointers with one-dimensional arrays in this free C programming course. You should know that arr[i] and *(arr + i) are interchangeable.
Array Name vs Pointer: The Core Difference
In most expressions the name of an array is converted to the address of its first element, and that is why beginners treat arr as a pointer. The conversion is only an expression rule. The array itself is a block of memory with a fixed location chosen by the compiler or linker. The name arr is a label for that block, not a variable that stores an address. A pointer, by contrast, is a real object holding an address that you may change at any time.
| Property | Array int arr[5] | Pointer int *p |
|---|---|---|
| What it is | A block of 5 consecutive int objects | One object that stores an address |
sizeof | 5 * sizeof(int), 20 bytes here | Size of an address, 8 bytes on 64-bit Linux |
| Can it be reassigned? | No, the name is not a modifiable lvalue | Yes, it can point elsewhere |
++ / -- | Compile error | Allowed, moves by one element |
| Own storage for elements | Yes | No, it only refers to storage somewhere else |
One block, two different ways to name it
Why arr++ Does Not Compile
The expression arr++ means “add one to arr and store the result back into arr“. That requires arr to be a modifiable lvalue, a named location that can receive a new value. An array name is not one, because the array cannot be moved in memory. GCC reports it like this:
int main(void)
{
int arr[3] = { 1, 2, 3 };
arr++;
return 0;
}error: lvalue required as increment operand
4 | arr++;
| ^~The fix is to copy the address into a pointer and move the pointer: int *p = arr; p++;. The array stays put, the pointer walks along it.
Array Name vs &Array: Same Address, Different Type
arr and &arr print the same numeric address, but they have different types, and the type controls pointer arithmetic.
| Expression | Type | Adding 1 moves by |
|---|---|---|
arr | int * (after decay) | sizeof(int), one element |
&arr | int (*)[5], pointer to a 5-element array | sizeof(arr), the whole array |
&arr[0] | int * | sizeof(int) |
This is why assigning &arr to an int * is a type mismatch. GCC says:
int main(void)
{
int arr[3] = { 1, 2, 3 };
int *p;
p = &arr;
return *p;
}warning: assignment to 'int *' from incompatible pointer type 'int (*)[3]' [-Wincompatible-pointer-types]The warning is easy to dismiss because the address value is right, but the type is wrong. Increment that pointer and you skip the entire array, which is almost never what you meant. If you really want a pointer to the whole array, declare it as int (*whole)[5].
Complete Example: Array Name, Pointer and &arr
#include <stdio.h>
int main(void)
{
int arr[5] = { 12, 24, 36, 48, 60 };
int *p = arr;
p++; /* legal: p is a variable */
printf("*p after p++ : %d\n", *p);
printf("sizeof(arr) : %zu\n", sizeof arr);
printf("sizeof(p) : %zu\n", sizeof p);
int (*whole)[5] = &arr; /* pointer to the WHOLE array */
printf("(*whole)[2] : %d\n", (*whole)[2]);
printf("step of arr+1 : %td bytes\n", (char *)(arr + 1) - (char *)arr);
printf("step of &arr+1: %td bytes\n", (char *)(&arr + 1) - (char *)&arr);
int *q = &arr[4];
printf("q[-1] = %d, *(q-2) = %d\n", q[-1], *(q - 2));
printf("(arr+1)[2] : %d\n", (arr + 1)[2]);
return 0;
}gcc -std=c17 -Wall -Wextra -o arr_vs_ptr arr_vs_ptr.c
./arr_vs_ptrExpected output on 64-bit Linux:
*p after p++ : 24
sizeof(arr) : 20
sizeof(p) : 8
(*whole)[2] : 36
step of arr+1 : 4 bytes
step of &arr+1: 20 bytes
q[-1] = 48, *(q-2) = 36
(arr+1)[2] : 48Pointer arithmetic on the same five integers
Walk through the output. p++ moved the pointer to the second element, so *p is 24. sizeof(arr) is the whole array, but sizeof(p) is only the pointer. Stepping arr + 1 advances 4 bytes, while &arr + 1 advances 20, the size of the complete array.
Warning: &arr + 1 points one past the end of the array. Forming that address is legal, but dereferencing it is undefined behaviour. The program above only subtracts addresses, which is allowed.
Negative Subscripts and the (arr + 1)[2] Form
Subscripting is defined as a[i] equals *(a + i). Nothing in that rule requires i to be non-negative or a to be an array name. If q points at arr[4], then q[-1] is *(q - 1), which is arr[3] (48), and *(q - 2) is arr[2] (36). The index just has to keep the final address inside the same array.
Likewise (arr + 1)[2] first forms a pointer to arr[1] and then subscripts it by 2, giving *(arr + 1 + 2), which is arr[3]. The same rule makes 2[arr] legal C, because *(2 + arr) is the same as *(arr + 2). It is a fun trivia question for interviews and a style crime in real code.
Where This Matters in Embedded C Programming
- **Function parameters:**
void send(int buf[])is silently rewritten asvoid send(int *buf). Inside the functionsizeof(buf)is the pointer size, so always pass the length separately. - **Lookup tables in flash:** a
constarray of calibration data is a fixed object; if you need to walk it, copy its address into aconst int *cursor. - **Ring buffers:** head and tail pointers move around a fixed array. The array name never changes, the pointers do.
- **Linker symbols:** a symbol such as
_enddeclared as an array can be used as an address, but its value is the address itself, not something stored there.
Common Mistakes and Troubleshooting
| Mistake | What happens | Fix |
|---|---|---|
arr++ or arr = other | Compile error about lvalue | Use a separate pointer to walk the array |
int *p = &arr; | Type mismatch warning; p + 1 skips the whole array | Write int *p = arr; or int *p = &arr[0]; |
Using sizeof(buf) on a function parameter | Returns pointer size, not array size | Pass the element count as an argument |
| Walking a pointer past the last element and dereferencing | Undefined behaviour | Compare against arr + N and stop before it |
Assuming arr and &arr behave alike in arithmetic | Off-by-array-size bugs | Remember the type decides the step size |
Best Practices, Performance and Security
- Use
sizeof arr / sizeof arr[0]in the scope where the array is defined to get its length. - Prefer
constpointers into read-only tables so the compiler flags accidental writes. - Pointer walking and indexing generate near-identical machine code on modern compilers, so choose whichever reads better.
- Security: out-of-range subscripts, including negative ones, are a frequent source of buffer overflows. Validate indexes that come from outside your code.
- Compile with
-Warray-bounds(included in recent-Walllevels with optimisation) to catch constant out-of-range accesses.
Summary and Key Takeaways
- An array name is the label of a fixed block; a pointer is a variable that holds an address.
- Array names convert to a pointer to the first element in most expressions, except with
sizeofand&. arr + 1moves one element;&arr + 1moves the whole array.p[-1]is valid when it stays inside the array, becausep[i]means*(p + i).- Function parameters declared as arrays are really pointers.
Conclusion
Once you separate the two ideas, many output-prediction puzzles in this free embedded C course stop being tricks. Ask two questions: is this name an array or a pointer, and what is the type of the expression I am adding to? The next lecture applies the same thinking to the increment and decrement operators combined with the dereference operator.
Frequently Asked Questions
Is an array name a pointer in C?
No. An array name refers to a block of elements. In most expressions it is converted to a pointer to its first element, but it is not a variable and cannot be modified.
Why can I increment a pointer but not an array name?
A pointer is a modifiable variable. An array name is not a modifiable lvalue, so arr++ is rejected by the compiler.
What is the difference between arr and &arr?
They share the same address but have different types. arr becomes int *, so adding 1 moves one element. &arr is a pointer to the entire array, so adding 1 moves past the whole array.
Are negative array indexes legal in C?
Yes, on a pointer that points inside an array. p[-1] equals *(p - 1). The resulting address must still lie within the same array.
Why does sizeof on an array parameter return the pointer size?
Because an array parameter is adjusted to a pointer type. Pass the length explicitly or wrap the array in a struct.
What does (arr + 2)[3] evaluate to?
It forms a pointer to arr[2] and then subscripts by 3, so it is arr[5]. It must still be inside the array for the access to be valid.
Continue the Free C Tutorial
Next up: how increment, decrement and dereference interact in pointer expressions.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments