问题
I know that the following is not correct:
int arr[2][3] = {}; //some array initialization here
int** ptr;
ptr = arr;
But I am quite surprised that the following lines actually work
int arr[2][3] = {}; //some array initialization here
auto ptr = arr;
int another_arr[2][3] = {}; //some array initialization here
ptr = another_arr;
Can anyone possibly explain what is the type assigned to ptr in the second block of code, and what happened underneath?
回答1:
Well, arrays decay to pointers when used practically everywhere. So naturally there's decay going on in your code snippet too.
But it's only the "outer-most" array dimension that decays to a pointer. Since arrays are row-major, you end up with int (*)[3]
as the pointer type, which is a pointer to a one-dimensional array, not a two dimensional array. It points to the first "row".
If you want ptr
's deduction to be a pointer to the array instead, then use the address-of operator:
auto ptr = &arr;
Now ptr
is int(*)[2][3]
.
回答2:
In
auto ptr = arr;
arr
decays into a pointer to its first element in the normal way; it's equivalent to
auto ptr = &arr[0];
Since arr[0]
is an array of three int
s, that makes ptr
a int (*)[3]
- a pointer to int[3]
.
another_arr
decays in exactly the same way, so in
ptr = another_arr;
both sides of the assignment have the type int (*)[3]
, and you can assign a T*
to a T*
for any type T
.
A pointer to arr
itself has type int(*)[2][3]
.
If you want a pointer to the array rather than a pointer to the array's first element, you need to use &
:
auto ptr = &arr;
回答3:
First, let's look at why you can't assign int arr[2][3]
to int **
. To make it easier to visualise, we'll initialise your array with a sequence, and consider what it looks like in memory:
int arr[2][3] = {{1,2,3},{4,5,6}};
In memory, the array data is stored as a single block, just like a regular, 1D array:
arr: [ 1, 2, 3, 4, 5, 6 ]
The variable arr
contains the address of the start of this block, and from its type (int[2][3]
) the compiler knows to interpret an index like arr[1][0]
as meaning "take the value that is at position (1*2 + 0) in the array".
However for a pointer-to-pointer (int**
), it is expected that the pointer-to-pointer contains either a single memory address or an array of memory addresses, and this/these adress(es) point to (an)other single int value or array of ints. Let's say we copied the array arr
into int **ptrptr
. In memory, it would look like this:
ptrptr: [0x203F0B20, 0x203F17D4]
0x203F0B20: [ 1, 2, 3 ]
0x203F17D4: [ 4, 5, 6 ]
So in addition to the actual int
data, an extra pointer must be stored for each row of the array. Rather than converting the two indexes into a single array lookup, access must be performed by making a first array lookup ("take the second value in ptrptr to get an int*"), then nother array lookup ("take the first value in the array at the address held by the previously obtained int*").
Here's a program that illustrates this:
#include <iostream>
int main()
{
int arr[2][3] = {{1,2,3},{4,5,6}};
std::cout << "Memory addresses for int arr[2][3]:" << std::endl;
for (int i=0; i<2; i++)
{
for (int j=0; j<3; j++)
{
std::cout << reinterpret_cast<void*>(&arr[i][j]) << ": " << arr[i][j] << std::endl;
}
}
std::cout << std::endl << "Memory addresses for int **ptrptr:" << std::endl;
int **ptrptr = new int*[2];
for (int i=0; i<2; i++)
{
ptrptr[i] = new int[3];
for (int j=0; j<3; j++)
{
ptrptr[i][j] = arr[i][j];
std::cout << reinterpret_cast<void*>(&ptrptr[i][j]) << ": " << ptrptr[i][j] << std::endl;
}
}
// Cleanup
for (int i=0; i<2; i++)
{
delete[] ptrptr[i];
ptrptr[i] = nullptr;
}
delete[] ptrptr;
ptrptr = nullptr;
return 0;
}
Output:
Memory addresses for int arr[2][3]:
0x7ecd3ccc0260: 1
0x7ecd3ccc0264: 2
0x7ecd3ccc0268: 3
0x7ecd3ccc026c: 4
0x7ecd3ccc0270: 5
0x7ecd3ccc0274: 6
Memory addresses for int **ptrptr:
0x38a1a70: 1
0x38a1a74: 2
0x38a1a78: 3
0x38a1a90: 4
0x38a1a94: 5
0x38a1a98: 6
Notice how the memory addresses always increase by 4 bytes for arr
, but for ptrptr
there is a jump of 24 bytes between values 3 and 4.
A simple assignment can't create the pointer-to-pointer structure needed for type int **
, which is why the loops were necessary in the above program. The best it can do is to decay the int[2][3]
type into a pointer to a row of that array, i.e. int (*)[3]
. That's what your auto ptr = arr;
ends up as.
回答4:
What is the type of [...]
Did you already try to ask the compiler to tell you the type of an expression?
int main()
{
int arr[2][3] = {{0,1,2}, {3,4,5}}; // <-- direct complete initialized here
auto ptr = arr; // <-- address assignment only
cout << "arr: " << typeid(arr).name() << endl;
cout << "ptr: " << typeid(ptr).name() << endl;
return 0;
}
I've to confess that the output
arr: A2_A3_i
ptr: PA3_i
seems to be not very readable at first glance (compared to some other languages), but when in doubt it may help. It's very compact, but one may get used to it soon. The encoding is compiler-dependent, in case you are using gcc, you may read Chapter 29. Demangling to understand how.
Edit:
some experimentation with some simple_cpp_name
function like this rudimentary hack
#include <typeinfo>
#include <cxxabi.h>
#include <stdlib.h>
#include <string>
std::string simple_cpp_name(const std::type_info& ti)
{
/// simplified code extracted from "Chapter 29. Demangling"
/// https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
char* realname = abi::__cxa_demangle(ti.name(), 0, 0, 0);
std::string name = realname;
free(realname);
return name;
}
will show you that auto &rfa = arr;
makes rfa
having the same type as arr
which is int [2][3]
.
来源:https://stackoverflow.com/questions/45409737/what-is-the-type-of-a-pointer-to-a-2d-array