Why Does Int*[] Decay into Int** But Not Int[][]

Why does int*[] decay into int** but not int[][]?

Why does int*[] decay into int** but not int[][]?

Because it would be impossible to do pointer arithmetic with it.

For example, int p[5][4] means an array of (length-4 array of int). There are no pointers involved, it's simply a contiguous block of memory of size 5*4*sizeof(int). When you ask for a particular element, e.g. int a = p[i][j], the compiler is really doing this:

char *tmp = (char *)p           // Work in units of bytes (char)
+ i * sizeof(int[4]) // Offset for outer dimension (int[4] is a type)
+ j * sizeof(int); // Offset for inner dimension
int a = *(int *)tmp; // Back to the contained type, and dereference

Obviously, it can only do this because it knows the size of the "inner" dimension(s). Casting to an int (*)[4] retains this information; it's a pointer to (length-4 array of int). However, an int ** doesn't; it's merely a pointer to (pointer to int).

For another take on this, see the following sections of the C FAQ:

  • 6.18: My compiler complained when I passed a two-dimensional array to a function expecting a pointer to a pointer.
  • 6.19: How do I write functions which accept two-dimensional arrays when the width is not known at compile time?
  • 6.20: How can I use statically- and dynamically-allocated multidimensional arrays interchangeably when passing them to functions?

(This is all for C, but this behaviour is essentially unchanged in C++.)

Does int (*)[] decay into int ** in a function parameter?

Only array types converted to pointer to its first element when passed to a function. a is of type pointer to an array of int, i.e, it is of pointer type and therefore no conversion.

For the prototype

void foo(int a[][10]);

compiler interpret it as

void foo(int (*a)[10]);  

that's because a[] is of array type. int a[][10] will never be converted to int **a. That said, the second para in that answer is wrong and misleading.

As a function parameter, int *a[] is equivalent to int ** this is because a is of array type .

Confusing explicit decay from array of int[] to pointer to int?

How can "an array of int[]" can be explicitly decayed to "a pointer to int"?

To be pedantic about terminology: "explicitly decaying" is an oxymoron. Decaying is by definition an implicit conversion.

To answer "how can [array] be explicitly be [converted] to [a pointer that isn't type of the first element]?":

It is because an array can decay to a pointer, and all data pointers can be explicitly converted (reinterpreted) to any other data pointer type. In this case, input decays to a int(*)[3] which is then explicitly converted to int*.

Although it is certainly well formed, another matter is whether indirecting through an explicitly reinterpreted pointer has defined behaviour. The rules around reinterpretation of pointers are complex and subtle - it's rarely safe to assume that it's guaranteed to behave the way you observe. I would be more confident writing instead:

aux[0] = *input;

Here, the input array decays to pointer to first subarray, this pointer is indirected to get lvalue, which then decays to pointer to the element of the subarray.


More generally, be very careful when using explicit conversion (T)expr or functional conversion T(expr) or reinterpret cast reinterpret_cast<T>(expr). Unless you can quote the standard rule which makes their use well defined, don't use them.

Relationship between int[n][n] and int**

int [3][3] is converted in expressions with rare exceptions (for example when used in the sizeof operator) to int ( * )[3].

int ( * )[3] is pointer to an array of type int[3] While int ** is pointer to an object of type int *

Consider the following example

int a[3][3];
int ( *pa )[3] = a;

int * b[3];
int **pb = b;

Or the last declaration you can write like

int * ( *pb ) = b;

In this example pointer pa is initialized with the address of the first element of two-dimensional array a. Elements of a two-dimensional array are one-dimensional arrays.

Pointer pb is also initialized by the address of the first element in this case of array b. Elements of the array are objects of type int *.

You can not convert one of these types into another.

1D array decays to pointer, but 2D array doesn't do so, why?

They have different memory layouts. A 2D array is one contiguous piece of memory while int** is an array of pointers. With a 2D array, the offset into a location is computed as rownum * collength + colnum (or vice versa depending on how you label rows/columns). That would not work with int**, which actually produces two memory reads; the first to get the column pointer, then the read of the data at an offset from that memory.

This different layout is, incidentally, why you must declare the array dimensions (all but the left-most) in functions that accept 2D arrays; otherwise it would not be possible for the compiler to generate the code to compute the offsets.

The following is an attempt at a picture of the memory layout of int**. The left column is a contiguous array of pointers where each contains the address of a contiguous piece of memory with the data. Note that the data in each column does not have to be the same length (although that might not necessarily be a good thing for code readability):

int **array; 
int i, j;
int cntr = 1;
array = malloc( 3 * sizeof( int* ));
for ( i = 0; i < 3; i++ ) {
array[i] = malloc( 4 * sizeof( int ));
for ( j = 0; j < 4; j++ )
array[i][j] = cntr++;
}

Gives something like this:

array ==> |addr1|  ==>  [1,2,3,4]
|addr2| ==> [5,6,7,8]
|addr3| ==> [9,10,11,12]

In contrast, This is what the layout might look like for int[3][4]. The brackets just show the logical break of each column of data. The integer values are contiguous in memory:

int array[3][4];
int i, j;
int cntr = 1;
for ( i = 0; i < 3; i++ )
for ( j = 0; j < 4; j++ )
array[i][j] = cntr++;

Gives something like this:

array ==> [1,2,3,4][5,6,7,8][9,10,11,12]

What is array to pointer decay?

It's said that arrays "decay" into pointers. A C++ array declared as int numbers [5] cannot be re-pointed, i.e. you can't say numbers = 0x5a5aff23. More importantly the term decay signifies loss of type and dimension; numbers decay into int* by losing the dimension information (count 5) and the type is not int [5] any more. Look here for cases where the decay doesn't happen.

If you're passing an array by value, what you're really doing is copying a pointer - a pointer to the array's first element is copied to the parameter (whose type should also be a pointer the array element's type). This works due to array's decaying nature; once decayed, sizeof no longer gives the complete array's size, because it essentially becomes a pointer. This is why it's preferred (among other reasons) to pass by reference or pointer.

Three ways to pass in an array1:

void by_value(const T* array)   // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])

The last two will give proper sizeof info, while the first one won't since the array argument has decayed to be assigned to the parameter.

1 The constant U should be known at compile-time.

Problem receiving an array as pointer parameter

An int* (a pointer-to-int) and an int*[] (an array of pointer-to-int) are two different things. And actually, in a function parameter, int* coordenadasFicha[2] is actually passed as int** (pointer to pointer-to-int), because an array decays into a pointer to its 1st element.

In your case, you are creating coordenadasFicha as an dynamic array of ints, but your function is expecting an array of int* pointers instead.

So, to do what you are attempting, you would need to do either this:

void pedirCoordenadasFicha(int* coordenadasFicha){
std::cin >> coordenadasFicha[0];
std::cin >> coordenadasFicha[1];
}

int* coordenadasFicha = new int[2];
pedirCoordenadasFicha(coordenadasFicha);
...
delete[] coordenadasFicha;

Or this:

void pedirCoordenadasFicha(int* coordenadasFicha[2]){
std::cin >> *coordenadasFicha[0];
std::cin >> *coordenadasFicha[1];
}

int** coordenadasFicha = new int*[2];
for(int i = 0; i < 2; ++i) {
coordenadasFicha[i] = new int;
}
pedirCoordenadasFicha(coordenadasFicha);
...
for(int i = 0; i < 2; ++i) {
delete coordenadasFicha;
}
delete[] coordenadasFicha;

Or, just get rid of new altogether, and pass the array by reference:

void pedirCoordenadasFicha(int (&coordenadasFicha)[2]){
std::cin >> coordenadasFicha[0];
std::cin >> coordenadasFicha[1];
}

int coordenadasFicha[2];
pedirCoordenadasFicha(coordenadasFicha);
...

C26485 and pointer decay with TCHAR in exception handler

This diagnostic is an unfortunate result of the code analysis taking too narrow a view, ignoring hints that are readily available. C26485 warns against array-to-pointer decay, one of C++' most dangerous features. When passing the name of an array to a function that expects a pointer, the compiler silently converts the array into a pointer to its first element, thereby dropping the size information that's part of the array type.

Clients that call into an interface that accepts individual arguments (pointer and size) to describe an array must make sure that the size actually matches that of the array. This has caused countless CVE's, and there's no reason to believe that the situation is getting any better. Array-to-pointer decay is dangerous and having tooling guard against it is great. In theory.

Here, however, things are different. The declaration (and definition) of GetErrorMessage have SAL Annotations that allow the compiler to verify, that pointer and size do match, at compile time. The signature is as follows:

virtual BOOL GetErrorMessage(_Out_writes_z_(nMaxError) LPTSTR lpszError,
_In_ UINT nMaxError,
_Out_opt_ PUINT pnHelpContext = NULL) const;

The _Out_writes_z_(s) annotation establishes a compile-time verifiable relationship between the pointer lpszError and its corresponding array's size nMaxError. This is helpful information that should be taken advantage of whenever possible.

First, though, let's try to address the immediate issue, following the recommendation from the documentation:

An explicit cast to the decayed pointer type prevents the warning, but it doesn't prevent buggy code.

The most compact way to turn an array into a pointer to its first element is to literally just do that:

catch (CDBException* e)
{
TCHAR szError[_MAX_PATH];
e->GetErrorMessage(&szError[0], _MAX_PATH);
AfxMessageBox(&szError[0]);
}

This fixes the immediate issue (on both function calls, incidentally, even if for different reasons). No more C26485's are issued, and—as an added bonus—passing an incorrect value as the second argument (e.g. _MAX_PATH + 1) does get the desired C6386 diagnostic ("buffer overrun").

This is crucially important, too, as a way of verifying correctness. If you were to use a more indirect way (say, by using a CString, as suggested here), you'd immediately give up on that compile-time verification. Using a CString is both computationally more expensive, and less secure.

As an alternative to the above, you could also temporarily suppress the C26485 diagnostic on both calls, e.g.

catch (CDBException* e)
{
TCHAR szError[_MAX_PATH];
// Decay is safe due to the _Out_writes_z_ annotation
#pragma warning(suppress : 26485)
e->GetErrorMessage(szError, _MAX_PATH);
// Decay is safe; the previous call guarantees zero-termination
#pragma warning(suppress : 26485)
AfxMessageBox(szError);
}

Which of those implementations you choose is ultimately a matter of personal preference. Either one addresses the issue, with the latter being maybe a bit more preserving as far as code analysis goes.

A word on why the final call to AfxMessageBox is safe: It expects a zero-terminated string, and thus doesn't need an explicit size argument. The _Out_writes_z_(s) annotation on the GetErrorMessage signature makes the promise to always zero-terminate the output string on return. This, too, is verified at compile time, on both sides of the contract: Callers can rely on receiving a zero-terminated string, and the compiler makes sure that the implementation has no return path that violates this post-condition.



Related Topics



Leave a reply



Submit