Pointer address in a C multidimensional array
I'm messing around with multidimensional arrays and pointers. I've been looking at a program that prints out the contents of, and addresses of, a simple array. Here's my array declaration:
int zippo[4][2] = { {2,4},
{6,8},
{1,3},
{5,7} };
My current understanding is that zippo
is a pointer, and it can hold the address of a couple of other pointers. By default, zippo
holds the address of pointer zippo[0]
, and it can also hold the addresses of pointers zippo[1]
, zippo[2]
, and zippo[3]
.
Now, take the following statement:
printf("zippo[0] = %p\n", zippo[0]);
printf(" *zippo = %p\n", *zippo);
printf(" zippo = %p\n", zippo);
On my machine, that gives the following output:
zippo[0] = 0x7fff170e2230
*zippo = 0x7fff170e2230
zippo = 0x7fff170e2230
I perfectly understand why zippo[0]
and *zippo
have the same value. They're both pointers, and they both store the address (by default) of the integer 2, or zippo[0][0]
. But what is up with zippo
also sharing the same memory address? Shouldn't zippo
be storing the address of the pointe开发者_如何学Cr zippo[0]
? Whaaaat?
When an array expression appears in most contexts, its type is implicitly converted from "N-element array of T" to "pointer to T", and its value is set to point to the first element in the array. The exceptions to this rule are when the array expression is an operand of either the sizeof
or address-of (&
) operators, or when the array is a string literal being used as an initializer in a declaration.
Thus, the expression zippo
"decays" from type int [4][2]
(4-element array of 2-element arrays of int) to int (*)[2]
(pointer to 2-element array of int). Similarly, the type of zippo[0]
is int [2]
, which is implicitly converted to int *
.
Given the declaration int zippo[4][2]
, the following table shows the types of various array expressions involving zippo and any implicit conversions:
Expression Type Implicitly converted to Equivalent expression ---------- ---- ----------------------- --------------------- zippo int [4][2] int (*)[2] &zippo int (*)[4][2] *zippo int [2] int * zippo[0] zippo[i] int [2] int * &zippo[i] int (*)[2] *zippo[i] int zippo[i][0] zippo[i][j] int &zippo[i][j] int * *zippo[i][j] invalid
Note that zippo
, &zippo
, *zippo
, zippo[0]
, &zippo[0]
, and &zippo[0][0]
all have the same value; they all point to the base of the array (the address of the array is the same as the address of the first element of the array). The types of the various expressions all differ, though.
When you declare a multidimensional array, the compiler treats it as a single dimensional array. Multidimensional arrays are just an abstraction to make our life easier. You have a misunderstanding: This isn't one array pointing to 4 arrays, its always just a single contigous block of memory.
In your case, doing:
int zippo[4][2]
Is really the same as doing
int zippo[8]
With the math required for the 2D addressing handled for you by the compiler.
For details, see this tutorial on Arrays in C++.
This is very different than doing:
int** zippo
or
int* zippo[4]
In this case, you're making an array of four pointers, which could be allocated to other arrays.
zippo
is not a pointer. It's an array of array values. zippo
, and zippo[i]
for i
in 0..4 can "decay" to a pointer in certain cases (particularly, in value contexts). Try printing sizeof zippo
for an example of the use of zippo
in a non-value context. In this case, sizeof
will report the size of the array, not the size of a pointer.
The name of an array, in value contexts, decays to a pointer to its first element. So, in value context, zippo
is the same as &zippo[0]
, and thus has the type "pointer to an array [2] of int
"; *zippo
, in value context is the same as &zippo[0][0]
, i.e., "pointer to int
". They have the same value, but different types.
I recommend reading Arrays and Pointers for answering your second question. The pointers have the same "value", but point to different amounts of space. Try printing zippo+1
and *zippo+1
to see that more clearly:
#include <stdio.h>
int main(void)
{
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
printf("%lu\n", (unsigned long) (sizeof zippo));
printf("%p\n", (void *)(zippo+1));
printf("%p\n", (void *)(*zippo+1));
return 0;
}
For my run, it prints:
32
0xbffede7c
0xbffede78
Telling me that sizeof(int)
on my machine is 4, and that the second and the third pointers are not equal in value (as expected).
Also, "%p"
format specifier needs void *
in *printf()
functions, so you should cast your pointers to void *
in your printf()
calls (printf()
is a variadic function, so the compiler can't do the automatic conversion for you here).
Edit: When I say an array "decays" to a pointer, I mean that the name of an array in value context is equivalent to a pointer. Thus, if I have T pt[100];
for some type T
, then the name pt
is of type T *
in value contexts. For sizeof
and unary &
operators, the name pt
doesn't reduce to a pointer. But you can do T *p = pt;
—this is perfectly valid because in this context, pt
is of type T *
.
Note that this "decaying" happens only once. So, let's say we have:
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5,7} };
Then, zippo
in value context decays to a pointer of type: pointer to array[2] of int
. In code:
int (*p1)[2] = zippo;
is valid, whereas
int **p2 = zippo;
will trigger an "incompatible pointer assignment" warning.
With zippo
defined as above,
int (*p0)[4][2] = &zippo;
int (*p1)[2] = zippo;
int *p2 = zippo[0];
are all valid. They should print the same value when printed using printf("%p\n", (void *)name);
, but the pointers are different in that they point to the whole matrix, a row, and a single integer respectively.
The important thing here is that int zippy[4][2]
is not the same type of object as int **zippo
.
Just like int zippi[5]
, zippy
is the address of a block of memory. But the compiler knows that you want to address the eight memory location starting at zippy
with a two dimensional syntax, but want to address the five memory location starting at zippi
with a one dimensional syntax.
zippo
is a different thing entirely. It holds the address of a a block of memory big enough to contain two pointer, and if you make them point at some arrays of integers, you can dereference them with the two dimensional array access syntax.
Very well explained by Reed, I shall add few more points to make it simpler, when we refer to zippo
or zippo[0]
or zippo[0][0]
, we are still referring to the same base address of the array zippo
. The reason being arrays are always contiguous block of memory and multidimensional arrays are multiple single dimension arrays continuously placed.
When you have to increment by each row, you need a pointer int *p = &zippo[0][0]
, and doing p++
increments the pointer by every row.
In your example id its a 4 X 2 array, on doing p++
its, pointer currently points to second set of 4 elements.
精彩评论