8

Can someone explain to me why technically a 2D array does not actually decay to int** whereas when you have a single dimensional array, you do get pointer decay to int* (if you had array of int).

I understand what pointer decay is, but I don't get why the 2D array doesn't exhibit this decay?

It's a common confusion with people new to C or C++, but I find myself tripping over it often too.

4
  • Well, it decays to a pointer to an array. I guess it's defined not to screw up your iteration by changing the size of each element. Commented Mar 8, 2013 at 14:15
  • 4
    There's no such thing as a 2D array in C (or C++), there are only arrays of arrays. Once you've discarded this misconception, your question is much simpler to answer. Commented Mar 8, 2013 at 14:37
  • You can always cast the array to (int *) if you want to discard the dimensions. Commented Mar 8, 2013 at 14:41
  • You may find this answer useful. Commented Mar 8, 2013 at 18:29

7 Answers 7

6

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]
Sign up to request clarification or add additional context in comments.

7 Comments

So an array of pointers is not necessarily contiguous?
Not generally, although it could be allocated as a single chunk and then partitioned into arrays of pointers within the chunk. But the offset computations would still be completely different.
care to give me an example for the pointer to pointer computation?
@TonyTheLion - the pointers in an array of pointers are contiguous (as are the elements in an array of any type of object). That's why the second decay doesn't happen: an array of objects is not an array of pointers to objects, even when the type of the objects is an array.
@TonyTheLion: There isn't a single computation. It requires two memory accesses, which I tried to explain in the answer. I'll see if I can clarify it.
|
4

The pattern is that an expression of type "N-element array of T" will be converted to an expression of type "pointer to T". If T is an array type, then you wind up with a pointer to an array. Given a declaration like

T arr[M][N];

the expression arr has type "M-element array of N-element arrays of T". Except when it is the operand of the sizeof, _Alignof, or unary & operators, it will be converted to an expression of type "pointer to N-element array of T", or T (*)[N].

When in doubt, write out the type in English, such as "four-element array of five-element arrays of six-element arrays of int", then replace the first "n-element array of" with "pointer to", giving "pointer to 5-element arrays of 6-element arrays of int":

Declaration                Expression          Type              Decays to
-----------                ----------          ----              ---------
int arr[4][5][6];                 arr          int [4][5][6]     int (*)[5][6]
                               arr[i]          int [5][6]        int (*)[6]
                            arr[i][j]          int [6]           int *
                                 &arr          int (*)[4][5][6]  n/a

1 Comment

Excellent technical answer.
3

The result of converting a two-dimensional array to a pointer to its first element is a pointer, not an array. Since it is not an array, it is not converted further.

2 Comments

Correct answer, but for more context you could add the information of the pointer type resulting from the decay of a 2-D array.
Correct answer, but for more context you could add the information of the pointer type resulting from the decay of a 2-D array.
2

It does decay. An expression whose type is "array of T" decays into a pointer to its first element. For a 2-dimensional array of T, the first element has type array of T. So:

int arr[5];

In most contexts, arr has type "pointer to int". Now apply the same rule to a 2-dimensional array:

int arr2[5][5];

Here, arr2 has type "pointer to array of 5 int". Note that the type of this object is not "array of T", so it does not decay further.

4 Comments

I believe arr2 would be "pointer to array of 6 int" not 5.
@Kludas - you're probably right. I always have to look up which is which. It's not relevant here, so I've changed it to use the same size for both dimensions, rather than deal with it properly. <g>.
Wait? you say it does decay at first, but then say it doesn't decay further at the end. I'm confused...
@TonyTheLion - I may have confused things by replying to the words in the title rather than the question itself. The top-level array part decays; the lower-level does not. So int arr2[5][5] decays into a pointer to array; the resulting type is not an array, it's a pointer, so it does not decay further.
1

A two-dimensional array is an array of arrays.

An "array of T" is - in most contexts - converted to a "pointer to T, so an "array of T[N]" is converted to a "pointer to T[N]".

Since the memory representation of an

int arr[M][N];

is completely different from

int **dptr = malloc(M*sizeof *dptr);
for(int i = 0; i < M; ++i) {
    dptr[i] = malloc(N*sizeof *dptr[i]);

an int[M][N] cannot even be converted to an int**.

Comments

1

It is common mistake that one might think that if int* i == int i[5] than int** i == int i[5][5] but it is not like that that for two reasons. First an int** is a pointer to a pointer and has nothing to do with the ways arrays are laid out and secondly a 2D array is actually 1D array internally and what it does is lay the second arrays side by side.

That means that in an array of [2][2] internally is creating an array of [4] and when you try and access [0][0] than that gets translated to [0], [0][1] will get translated to [1], [1][0] will get translated to [2] and [1][1] will get translated to [3]

Example:

#include <iostream>

void Print(int* i, int element)
{
    std::cout << i[element] << std::endl;
}

int main()
{
    int i[5][5];

    i[2][2] = 125;

    Print((i[0]), 12);
}

This will print 125 because I set [2][2] as 125 and that will be consider as equal to 12 because the first [0] will occupy from 0 - 4, [1] will occupy from 5-10 and so on and so forth.

2 Comments

Irrelevant implementation details are not the way to explain language semantics.
This doesn’t actually explain why it cannot be converted to an int**.
-1

A 2D array is a contiguous block of memory. When accessing it as a 2D array you need row length to jump from column to column. A pointer to a pointer is exactly that. It can simulate a 2D array if the first pointer is an array of pointers to a second dimension of arrays but it is a very different beast to the contiguous block of memory that a 2D array is.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.