19

If the following assignment is valid:

int a[2] = {1,2};
int* b = a;

then what is wrong with this:

int a[2][2]={1,2,3,4};
int** b = a;

C++ gives an error that it can't convert int[][] to int**. What is difference between the two types if int[] is the same as int*?

Puppy
  • 144,682
  • 38
  • 256
  • 465
bhuwansahni
  • 1,834
  • 1
  • 14
  • 20

4 Answers4

31

Take it easy. It is only a compiler error. Arrays are pretty tricky. Here is the rule:

The value of a variable of type array decays to the address of element zero of this array

Your first snippet looks like:

int a[2] = {1,2};

So according to the rule if a is in the right hand side of a assignment then it decays to address of the element zero and that is why it has type int *. This brings you to

int *b = a;

In the second snippet what you really have is an array of arrays. (By the way, to make it explicit I've changed your code a bit.)

int a[2][2]={{1,2},{3,4}};

This time a will decay to the pointer to an array of two integers! So if you would want to assign a to something, you would need this something to have the same type.

int (*b)[2] = a; //Huh!

(This syntax maybe a bit stunning to you, but just think for a moment that we have written int *b[2]; Got the point? b would be an array of pointers to integers! Not really what we wanted...)

You could stop reading here, but you could also move on, because I have not told you all the truth. The rule I mentioned has three exceptions...

The value of the array will not decay to the address of the element zero if

  1. array is operand of sizeof
  2. array is operand of &
  3. array is a literal string initializer for a character array

Let's explain these exceptions in more detail and with examples:

int a[2];

int *pi = a ; /* the same as pi = &a[0]; */

printf("%d\n", sizeof(a)); /* size of the array, not of a pointer is printed! */

int (*pi2)[2] = &a; /* address of the array itself is taken (not the address of a pointer) */

And finally

char a[] = "Hello world ";

Here not a pointer to "Hello world" is copied, but the whole string is copied and a points to this copy.

There is really a lot of information and it is really difficult to understand everything at once, so take your time. I recommend you to read K&R on this topic and afterwards this excellent book.

caf
  • 233,326
  • 40
  • 323
  • 462
Roman Byshko
  • 8,591
  • 7
  • 35
  • 57
  • 12
    +1, calming the developer when debugging in C++ is usually the first step. :D – Russell Dec 06 '11 at 04:16
  • It's not a warning, it's a fundamental incompatibility in the binary layout of the two types. – Chris Lutz Dec 06 '11 at 04:18
  • @ChrisLutz Thank you, just my bad English. – Roman Byshko Dec 06 '11 at 04:23
  • 2
    OP note that while `b` is valid and it'll compile, in order to use it to access the elements of `a` you're going to need to specify the bounds, ie. `int (*b)[2] = a;`. – AusCBloke Dec 06 '11 at 04:30
  • @AusCBloke There is so much to say about it that for a beginner is just way tooo much :( – Roman Byshko Dec 06 '11 at 04:33
  • Yeah true. @bhuwansahni: you should take a read of K&R. Anyway +1. – AusCBloke Dec 06 '11 at 04:34
  • You say "`a` is address of the element zero and has type `int *`", but `int a[]` is not the same as `int *a`. See http://cplusplus.com/forum/articles/10/ – Timothy Jones Dec 06 '11 at 04:52
  • @TimothyJones Yes.. I'd need to improve it. What I really want to say is that `a` decay to ... if `a` is in the right side of the assignment. – Roman Byshko Dec 06 '11 at 04:54
  • I would say something like "if you assign an `int[]` to an `int*` the compiler does the conversion for you" (you illustrate this in your example later in your answer). I'm not a fan of the use of "decay", as there are actually more steps to access an `int*` than there are to access an `int[]`. – Timothy Jones Dec 06 '11 at 04:58
  • @TimothyJones I'm using decay, because it's kind of terminology that is used in books. The second point you make is also very important. But this is a material for another question or so. – Roman Byshko Dec 06 '11 at 05:01
  • 3
    Good answer (way too many people misunderstand this). Suggested reading: section 6 of the [comp.lang.d FAQ](http://c-faq.com). – Keith Thompson Dec 06 '11 at 05:06
  • +1 I've learned that there is still another situation when an array is not converted to a pointer: when using an array to initialize a reference to the array.Is it right? – Archeosudoerus Dec 06 '11 at 06:13
  • @zyc you mean this one `int (*pi2)[2] = &a;`? If so, call it pointer in order not to mix it with C++ references. – Roman Byshko Dec 06 '11 at 06:36
  • Actually I mean this: `void foo(int (&arr)[10]){/*...*/}`. In this function the parameter is a reference to an array of 10 ints.And the array does not decay to a pointer points to this array. – Archeosudoerus Dec 06 '11 at 08:21
  • I this not c++? In c++ you hqve to turn it around and ask "when *does* the array decay?". It does only if needes in c++! *Much* less often than in c, which your answer applies to. – Johannes Schaub - litb Dec 06 '11 at 08:39
  • @zyc Ah, you are right, I haven't taken a look at this example here. – Roman Byshko Dec 06 '11 at 14:11
  • @JohannesSchaub-litb *"when does the array decay?"* At the beginning I've said that it always decays, however *later* I added three exceptions. zyc gaved me another one. Are there something else that should be mentioned? – Roman Byshko Dec 06 '11 at 14:15
  • @KeithThompson Kudos for supplying an amazing link! – Ognyan M Oct 07 '18 at 16:20
7

This is something that comes up a lot, so I will attempt to explain it as clearly as I can.

When you make an array, it stores the elements contiguously in memory, so:

int arr[2] = { 1, 2 };

Translates to:

arr:
+---+---+
| 1 | 2 |
+---+---+

A pointer points to an object in memory, and when dereferenced, via unary * or via [], it accesses that contiguous memory. So after

int *ptr = arr;

ptr (or &ptr[0] if you like) points to the box 1 is in, and ptr + 1 (or &ptr[1]) points to the box 2 is in. This makes sense.

But if arrays are contiguous in memory, arrays of arrays are contiguous in memory. So:

int arr[2][2] = {{ 1, 2 }, { 3, 4 }};

Looks in memory like this:

arr:
+---+---+---+---+
| 1 | 2 | 3 | 4 |
+---+---+---+---+

Which looks a lot like our flat array.

Now, let's consider how a pointer to a pointer to an int would be laid out in memory:

ptr:
+-------+-------+
| &sub1 | &sub2 |
+-------+-------+

sub1:
+---+---+
| 1 | 2 |
+---+---+

sub2:
+---+---+
| 3 | 4 |
+---+---+

ptr (or &ptr[0]) points to sub1, and ptr + 1 (or &ptr[1]) points to sub2. sub1 and sub2 have no actual relation to each other, and can be anywhere in memory, but because it's a pointer to a pointer, the double-dereference of a 2D array is preserved, even though the memory structure is not compatible.

Arrays of type T decay to pointers to type T, but arrays of arrays of type T do not decay to pointers to pointers to type T, they decay to pointers to arrays of type T. So when our 2D arr decays to a pointer, it is not a pointer to a pointer to an int, but a pointer to an int [2]. The full name of this type is int (*)[2], and to make your line of code work you'd want to use

int (*ptr)[2] = arr;

Which is the correct type. ptr expects to point to a contiguous array of memory, like arr does - ptr (or &ptr[0]) points to arr and ptr + 1 (or &ptr[1]) points to &arr[1]. ptr[0] points to the box that holds 1, and ptr[1] points to the box that holds 3, so ptr[0][0] yields 1, ptr[0][1] yields 2, and so on.

Why do you need to know this? 2D pointers seem more complicated than they're worth - if you were using malloc you'd have to call malloc repeatedly in a loop, and do the same for free. OR, you could use some evil* trickery to make a flat, 1-dimensional allocation of memory act like a 2D array:

// x and y are the first and second dimensions of your array
// so it would be declared T arr[x][y] if x and y were static

int (*arr)[y] = malloc(x * y * sizeof(arr[0][0]));
if(!arr) /* error */;

Now arr points to a contiguous block of arrays of size y of int objects. Since the object it points to is an array, we don't need the double-pointer-indirection of int ** objects, and when you're done, you can free it with one call:

free(arr);

Compare this to a version using int **:

int **arr = malloc(x * sizeof(*arr));
if(!arr) /* error */;
for(size_t ii = 0; ii < x; ii++)
  {
    arr[ii] = malloc(y * sizeof(**arr));
    if(!arr[ii])
      {
        free(arr[ii]);
        free(arr);
      }
  }
// do work
for(size_t ii = 0; ii < x; ii++)
    free(arr[ii]);
free(arr);

The above code has a memory leak. See if you can find it. (Or just use the version with those seemingly tricky pointers-to-arrays.)

Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • As a side note, `int (*ptr)[2]` may look confusing, and rightly so. The command-line (and now web-based) tool [cdecl](http://cdecl.org) can help you decode C declarations into plain English. – Chris Lutz Dec 06 '11 at 04:48
4

The famous decay convention: an array is treated as a pointer that points to the first element of the array.

int a[2] = {1,2};
int* b = a; //decay

But the decay convention shouldn't be applied more than once to the same object.

int a[2][2]={1,2,3,4};
int** b = a; //decay more than once
Eric Z
  • 14,327
  • 7
  • 45
  • 69
2

int[] isn't the same as int*. int[] will decay into an int* in certain contexts.

You probably should read the comp.lang.c FAQ, particularly:

and perhaps the rest of the Array and Pointers section.

jamesdlin
  • 81,374
  • 13
  • 159
  • 204