I have the following C program:
#include
int main(){
int a[2][2] = {1, 2, 3, 4};
printf(\"a:%p, &a:%p, *a:%p \\n\", a, &a, *
They're not identical pointers. They're pointers of distinct types that all point to the same memory location. Same value (sort of), different types.
A 2-dimensional array in C is nothing more or less than an array of arrays.
The object a
is of type int[2][2]
, or 2-element array of 2-element array of int
.
Any expression of array type is, in most but not all contexts, implicitly converted to ("decays" to) a pointer to the array object's first element. So the expression a
, unless it's the operand of unary &
or sizeof
, is of type int(*)[2]
, and is equivalent to &a[0]
(or &(a[0])
if that's clearer). It becomes a pointer to row 0 of the 2-dimensional array. It's important to remember that this is a pointer value (or equivalently an address), not a pointer object; there is no pointer object here unless you explicitly create one.
So looking at the several expressions you asked about:
&a
is the address of the entire array object; it's a pointer expression of type int(*)[2][2]
.a
is the name of the array. As discussed above, it "decays" to a pointer to the first element (row) of the array object. It's a pointer expression of type int(*)[2]
.*a
dereferences the pointer expression a
. Since a
(after it decays) is a pointer to an array of 2 int
s, *a
is an array of 2 int
s. Since that's an array type, it decays (in most but not all contexts) to a pointer to the first element of the array object. So it's of type int*
. *a
is equivalent to &a[0][0]
.&a[0]
is the address of the first (0th) row of the array object. It's of type int(*)[2]
. a[0]
is an array object; it doesn't decay to a pointer because it's the direct operand of unary &
.&a[0][0]
is the address of element 0 of row 0 of the array object. It's of type int*
.All of these pointer expressions refer to the same location in memory. That location is the beginning of the array object a
; it's also the beginning of the array object a[0]
and of the int
object a[0][0]
.
The correct way to print a pointer value is to use the "%p"
format and to convert the pointer value to void*
:
printf("&a = %p\n", (void*)&a);
printf("a = %p\n", (void*)a);
printf("*a = %p\n", (void*)*a);
/* and so forth */
This conversion to void*
yields a "raw" address that specifies only a location in memory, not what type of object is at that location. So if you have multiple pointers of different types that point to objects that begin at the same memory location, converting them all to void*
yields the same value.
(I've glossed over the inner workings of the []
indexing operator. The expression x[y]
is by definition equivalent to *(x+y)
, where x
is a pointer (possibly the result of the implicit conversion of an array) and y
is an integer. Or vice versa, but that's ugly; arr[0]
and 0[arr]
are equivalent, but that's useful only if you're writing deliberately obfuscated code. If we account for that equivalence, it takes a paragraph or so to describe what a[0][0]
means, and this answer is probably already too long.)
For the sake of completeness the three contexts in which an expression of array type is not implicitly converted to a pointer to the array's first element are:
&
, so &arr
yields the address of the entire array object;sizeof
, so sizeof arr
yields the size in bytes of the array object, not the size of a pointer; andchar s[6] = "hello";
copies the array value into s
rather than nonsensically initializing an array object with a pointer value. This last exception doesn't apply to the code you're asking about.(The N1570 draft of the 2011 ISO C standard incorrectly states that _Alignof
is a fourth exception; this is incorrect, since _Alignof
can only be applied to a parenthesized type name, not to a expression. The error is corrected in the final C11 standard.)
Recommended reading: Section 6 of the comp.lang.c FAQ.
This also means that in C arrays have no overhead. In some other languages the structure of arrays is
&a --> overhead
more overhead
&a[0] --> element 0
element 1
element 2
...
and &a != &a[0]
You know that a
is the address of the first element of your array and according to the C standard, a[X]
is equal to *(a + X)
.
So:
&a[0] == a
because &a[0]
is the same as &(*(a + 0))
= &(*a)
= a
.
&a[0][0] == a
because &a[0][0]
is the same as &(*(*(a + 0) + 0)))
= &(*a)
= a
Intuitively, now the reason is clear behind the output. But, considering how pointers are implemented in C, I can't understand how a and &a are equal. I am assuming that there is a variable a in memory which points to the array(and the starting address of this array-memory-block would be the value of this variable a).
Well, no. There is no such thing as an address stored anywhere in memory. There is only memory allocated for the raw data, and that's it. What happens is, when you use a naked a
, it immediately decays into a pointer to the first element, giving the impression that the 'value' of a
were the address, but the only value of a
is the raw array storage.
As a matter of fact, a
and &a
are different, but only in type, not in value. Let's make it a bit easier by using 1D arrays to clarify this point:
bool foo(int (*a)[2]) { //a function expecting a pointer to an array of two elements
return (*a)[0] == (*a)[1]; //a pointer to an array needs to be dereferenced to access its elements
}
bool bar(int (*a)[3]); //a function expecting a pointer to an array of three elements
bool baz(int *a) { //a function expecting a pointer to an integer, which is typically used to access arrays.
return a[0] == a[1]; //this uses pointer arithmetic to access the elements
}
int z[2];
assert((size_t)z == (size_t)&z); //the value of both is the address of the first element.
foo(&z); //This works, we pass a pointer to an array of two elements.
//bar(&z); //Error, bar expects a pointer to an array of three elements.
//baz(&z); //Error, baz expects a pointer to an int
//foo(z); //Error, foo expects a pointer to an array
//bar(z); //Error, bar expects a pointer to an array
baz(z); //Ok, the name of an array easily decays into a pointer to its first element.
As you see, a
and &a
behave very differently, even though they share the same value.
+------------------------------+
| a[0][0] <-- a[0] <-- a | // <--&a, a,*a, &a[0],&a[0][0]
|_a[0][1]_ |
| a[1][0] <-- a[1] |
| a[1][1] |
+------------------------------+
A 2D array in C is treated as a 1D array whose elements are 1D arrays (the rows).
For example, a 4x3 array of T
(where "T" is some data type) may
be declared by: T a[4][3]
, and described by the following
scheme:
+-----+-----+-----+
a == a[0] ---> | a00 | a01 | a02 |
+-----+-----+-----+
+-----+-----+-----+
a[1] ---> | a10 | a11 | a12 |
+-----+-----+-----+
+-----+-----+-----+
a[2] ---> | a20 | a21 | a22 |
+-----+-----+-----+
+-----+-----+-----+
a[3] ---> | a30 | a31 | a32 |
+-----+-----+-----+
Also the array elements are stored in memory row after row.
Prepending the T
and appending the [3]
to a
we have an array of 3
elements of type T
. But, the name a[4]
is itself an array indicating that there are 4
elements each being an array of 3
elements. Hence we have an array of 4
arrays of 3
elements each.
Now it is clear that a
points to the first element (a[0]
) of a[4]
. On the Other hand &a[0]
will give the address of first element (a[0]
) of a[4]
and &a[0][0]
will give the address of 0th
row (a00 | a01 | a02)
of array a[4][3]
. &a
will give the address of 2D array a[3][4]
. *a
decays to pointers to a[0][0]
.
Note that a
is not a pointer to a[0][0]
; instead it is a pointer to a[0]
.
Hence
- G1:
a
and&a[0]
are equivalent.- G2:
*a
,a[0]
and&a[0][0]
are equivalent.- G3:
&a
(gives the address of 2D arraya[3][4]
).
But group G1, G2 and G3 are not identical although they are giving the same result (and I explained above why it is giving same result).