Its called typed pointer math (or typed-pointer-arithmetic) and is intuitive when you get one thing engrained in your DNA: Pointer math adjusts addresses based on the type of a pointer that holds said-address.
In your example, what is the type of x
? It is an array of int
. but what is the type of the expression x
? Hmmm. According to the standard, the expression value of x
is the address of the first element of the array, and the type is pointer-to-element-type, in this case, pointer-to-int
.
The same standard dictates that for any data var (functions are a little odd) using the &
operator results in an address with a type of pointer-to-type, the type being whatever the type of the variable is:
For example, given
int a;
the expression &a
results in an address who's type is int *
. Similarly,
struct foo { int x,y,z } s;
the expression &s
results in an address who's type is struct foo *
.
And now, the point of probable confusion, given:
int x[5];
the expression &x
results in an address who's type is int (*)[5]
, i.e. a pointer to an array of five int
. This is markedly different than simply x
which is, per the standard, evals as an address who's type is a pointer to the underlying array element type
Why does it matter? Because all pointer arithmetic is based on that fundamental type of the expression address. Adjustments therein using typed pointer math are reliant on that fundamental concept.
int x[5];
x + 1
is effectively doing this:
int x[5];
int *p = x;
p + 1 // results is address of next int
Whereas:
&x + 1
is effectively doing this:
int x[5];
int (*p)[5] = &x;
p + 1 // results in address of next int[5]
// (which, not coincidentally, there isn't one)
Regarding the sizeof()
differential, once again, those pesky types come home to roost, and in particular difference, it is important to note that sizeof
is a compile-time operator; not run-time:
int x[5]
size_t n = sizeof(x);
In the above, sizeof(x)
equates to sizeof(type-of x)
. Since x
is int[5]
and int
is apparently 4 bytes on your system, the result is 20. Similarly,
int x[5];
size_t n = sizeof(*x);
results with sizeof(type-of *x)
begin assigned to n
. Because *x
is of type int
, this is synonymous with sizeof(int)
. The compile-time aspects, incidentally, make the following equally valid, though admittedly it looks a little dangerous at first glance:
int *p = NULL;
size_t n = sizeof(*p);
Just as before, sizeof(type-of *p)
equates to sizeof(int)
But what about:
int x[5];
size_t n = sizeof(&x);
Here again, sizeof(&x)
equates to sizeof(type-of &x)
. but we just covered what type &x
is; its int (*)[5]
. I.e. Its a data pointer type, and as such, its size will be the size of a pointer. On your rig, you apparently have 32bit pointers, since the reported size is 4.
An example of how &x
is a pointer type, and that indeed all data pointer types result in a similar size, I close with the following example:
#include <stdio.h>
int main()
{
int x[5];
double y[5];
struct foo { char data[1024]; } z[5];
printf("%zu, %zu, %zu\n", sizeof(x[0]), sizeof(x), sizeof(&x));
printf("%zu, %zu, %zu\n", sizeof(y[0]), sizeof(y), sizeof(&y));
printf("%zu, %zu, %zu\n", sizeof(z[0]), sizeof(z), sizeof(&z));
return 0;
}
Output (Mac OSX 64bit)
4, 20, 8
8, 40, 8
1024, 5120, 8
Note the last value size reports are identical.