Where the confusion comes from
Almost every C book says "an array name is a pointer". It is not. It decays into one in most expressions, which is close enough to fool you until the day it costs you marks.
Here is the truth in one line: an array is a block of memory. A pointer is a variable holding an address. In most expressions the array name is converted to the address of its first element. In three specific places, it is not.
Where they behave the same
#include <stdio.h>
int main(void)
{
int a[5] = {10, 20, 30, 40, 50};
int *p = a; /* same as int *p = &a[0]; */
printf("%d %d\n", a[2], p[2]); /* 30 30 */
printf("%d %d\n", *(a + 2), *(p + 2)); /* 30 30 */
return 0;
}
a[2] is defined by the standard as (a + 2). That is why indexing works on pointers too, and why the strange-looking 2[a] also compiles and gives 30 — because (2 + a) is the same thing. Do not write that in real code, but if it appears in a paper, now you know.
Pointer arithmetic scales by type
#include <stdio.h>
int main(void)
{
int a[5] = {10, 20, 30, 40, 50};
int *p = a;
printf("p = %p\n", (void *)p);
printf("p + 1 = %p\n", (void *)(p + 1)); /* 4 bytes further */
printf("*(p+1)= %d\n", *(p + 1)); /* 20 */
printf("&a[3] - &a[0] = %ld\n", (long)(&a[3] - &a[0])); /* 3, not 12 */
return 0;
}
p + 1 adds 1 * sizeof(int) bytes, not 1 byte. Subtracting two pointers gives the number of elements between them, not bytes. The compiler does the multiplication and division for you.
Where they are different: sizeof
#include <stdio.h>
int main(void)
{
int a[5];
int *p = a;
printf("sizeof(a) = %zu\n", sizeof(a)); /* 20 */
printf("sizeof(p) = %zu\n", sizeof(p)); /* 8 */
return 0;
}
sizeof is the first place the illusion breaks. sizeof(a) is the size of the whole array — 5 ints, 20 bytes. sizeof(p) is the size of one pointer, 8 bytes on a 64-bit machine.
This gives you the array-length trick, which works only where the real array is visible:
#include <stdio.h>
int main(void)
{
double d[7];
size_t n = sizeof(d) / sizeof(d[0]);
printf("%zu elements\n", n); /* 7 */
return 0;
}
Where they are different: &
&a is the second place. Its type is "pointer to array of 5 ints", so &a + 1 jumps 20 bytes, not 4:
#include <stdio.h>
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
printf("a = %p\n", (void *)a);
printf("&a[0] = %p\n", (void *)&a[0]); /* same number as a */
printf("&a = %p\n", (void *)&a); /* same number again */
printf("a + 1 = %p\n", (void *)(a + 1)); /* +4 */
printf("&a + 1 = %p\n", (void *)(&a + 1)); /* +20 */
return 0;
}
Three of these print the same number and mean different things. a is the address of the first int. &a is the address of the whole array. Same house number, different sized object.
Where they are different: assignment
#include <stdio.h>
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
int b[5];
int *p;
p = a; /* fine: a pointer can be assigned */
/* a = b; */ /* error: an array name is not assignable */
/* a++; */ /* error, same reason */
p++; /* fine */
printf("%d\n", *p); /* 2 */
return 0;
}
An array name is not a variable you can move. It is a fixed label on a fixed block. A pointer can be re-pointed all day.
The most common consequence of the decay rule: sizeof inside a function that took an array parameter gives the pointer size, always. This is wrong and will silently loop the wrong number of times. Always pass the length as a separate argument.
#include <stdio.h>
/* WRONG */
void print_bad(int arr[])
{
size_t n = sizeof(arr) / sizeof(arr[0]); /* 8 / 4 = 2, always */
size_t i;
for (i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
}
/* RIGHT */
void print_good(const int *arr, size_t n)
{
size_t i;
for (i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
}
int main(void)
{
int a[5] = {1, 2, 3, 4, 5};
print_bad(a); /* prints only 1 2 */
print_good(a, 5); /* prints 1 2 3 4 5 */
return 0;
}
int arr[], int arr[5] and int *arr in a parameter list are all exactly the same thing: a pointer. The 5 is ignored by the compiler. Writing it is a comment, not a constraint.
Strings make it visible
#include <stdio.h>
int main(void)
{
char arr[] = "hello"; /* 6 bytes on the stack, modifiable */
char *ptr = "hello"; /* pointer to read-only string data */
arr[0] = 'H'; /* fine */
/* ptr[0] = 'H'; */ /* undefined behaviour, usually a crash */
printf("%s %s\n", arr, ptr);
printf("%zu %zu\n", sizeof(arr), sizeof(ptr)); /* 6 8 */
return 0;
}
char arr[] = "hello" copies the six bytes into your array. char *ptr = "hello" stores the address of a string literal, which usually lives in a read-only section of the executable. Writing through it crashes on most systems.
If you want to modify a string, declare it as an array. If you only want to read it, const char * says so clearly and lets the compiler catch mistakes.
Quick summary
| Question | Array a |
Pointer p |
|---|---|---|
| What is it | a block of memory | a variable holding an address |
sizeof |
size of the whole block | 8 |
| Can be assigned | no | yes |
&a type |
pointer to whole array | pointer to pointer |
| As a function parameter | becomes a pointer | stays a pointer |