40

I tried to assign two fixed-size arrays to an array of pointers to them, but the compiler warns me and I don't understand why.

int A[5][5];
int B[5][5];
int*** C = {&A, &B};

This code compiles with the following warning:

warning: initialization from incompatible pointer type [enabled by default]

If I run the code, it will raise a segmentation fault. However, if I dynamically allocate A and B, it works just fine. Why is this?

dbush
  • 205,898
  • 23
  • 218
  • 273
Viridya
  • 640
  • 5
  • 13
  • 5
    Hint: check the data types – Sourav Ghosh May 20 '16 at 14:09
  • 3
    "if i declare A and B as dynamic array...". C has no such feature. You cannot "declare A and B as dynamic array". You can only declare them as pointers and then *manually* allocate memory to make them *behave* as dynamic multi-dimensional arrays. Such "manually built" arrays are not compatible with built-in multi-dimensional arrays - they are two completely different things. – AnT stands with Russia May 20 '16 at 14:36
  • This code is producing UB. "The initializer for a scalar shall be a single expression, optionally enclosed in braces." GCC and clang accept a lot of crappy code (as an extension?). Compiling using msvc will produce error (life [example](http://rextester.com/IKGWF36603)). – AnArrayOfFunctions Jun 11 '16 at 20:29
  • Just say var C={&A, &B}, Oh, wait, this is C-what? – FastAl Jun 15 '16 at 17:31

9 Answers9

25

If you want a declaration of C that fits the existing declarations of A and B you need to do it like this:

int A[5][5];
int B[5][5];
int (*C[])[5][5] = {&A, &B};

The type of C is read as "C is an array of pointers to int [5][5] arrays". Since you can't assign an entire array, you need to assign a pointer to the array.

With this declaration, (*C[0])[1][2] is accessing the same memory location as A[1][2].

If you want cleaner syntax like C[0][1][2], then you would need to do what others have stated and allocate the memory dynamically:

int **A;
int **B;
// allocate memory for A and each A[i]
// allocate memory for B and each B[i]
int **C[] = {A, B};

You could also do this using the syntax suggested by Vlad from Moscow:

int A[5][5];
int B[5][5];
int (*C[])[5] = {A, B};

This declaration of C is read as "C is an array of pointers to int [5] arrays". In this case, each array element of C is of type int (*)[5], and array of type int [5][5] can decay to this type.

Now, you can use C[0][1][2] to access the same memory location as A[1][2].

This logic can be expanded to higher dimensions as well:

int A[5][5][3];
int B[5][5][3];
int (*C[])[5][3] = {A, B};
dbush
  • 205,898
  • 23
  • 218
  • 273
19

Unfortunately there's a lot of crappy books/tutorials/teachers out there who will teach you wrong things....

Forget about pointer-to-pointers, they have nothing to do with arrays. Period.

Also as a rule of thumb: whenever you find yourself using more than 2 levels of indirection, it most likely means that your program design is fundamentally flawed and needs to be remade from scratch.


To do this correctly, you would have to do like this:

A pointer to an array int [5][5] is called array pointer and is declared as int(*)[5][5]. Example:

int A[5][5];
int (*ptr)[5][5] = &A;

If you want an array of array pointers, it would be type int(*[])[5][5]. Example:

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};

As you can tell this code looks needlessly complicated - and it is. It will be a pain to access the individual items, since you will have to type (*arr[x])[y][z]. Meaning: "in the array of array pointers take array pointer number x, take the contents that it points at - which is a 2D array - then take item of index [y][z] in that array".

Inventing such constructs is just madness and nothing I would recommend. I suppose the code can be simplified by working with a plain array pointer:

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};
int (*ptr)[5][5] = arr[0];
...
ptr[x][y][z] = 0;

However, this is still somewhat complicated code. Consider a different design entirely! Examples:

  • Make a 3D array.
  • Make a struct containing a 2D array, then create an array of such structs.
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I see great value in your rule of thumb. C++ doesn't _forbid_ constructs like this, but it does allow you to avoid them. – Drew Dormann Jun 10 '16 at 04:30
14

There is a lot wrong with the line

int*** C = {&A, &B};

You're declaring a single pointer C, but you're telling it to point to multiple objects; that won't work. What you need to do is declare C as an array of pointers to those arrays.

The types of both &A and &B are int (*)[5][5], or "pointer to 5-element array of 5-element array of int"; thus, the type of C needs to be "array of pointer to 5-element array of 5-element array of int", or

int (*C[2])[5][5] = { &A, &B };

which reads as

      C           -- C is a
      C[2]        -- 2-element array of
     *C[2]        -- pointers to
    (*C[2])[5]    -- 5-element arrays of
    (*C[2])[5][5] -- 5-element arrays of
int (*C[2])[5][5] -- int

Yuck. That's pretty damned ugly. It gets even uglier if you want to access an element of either A or B through C:

int x = (*C[0])[i][j]; // x = A[i][j]
int y = (*C[1])[i][j]; // y = B[i][j]

We have to explicitly dereference C[i] before we can index into the array it points to, and since the subscript operator [] has higher precedence than the unary * operator, we need to group *C[0] in parens.

We can clean this up a little bit. Except when it is the operand of the sizeof or unary & operators (or is a string literal being used to initialize another array in a declaration), an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element of the array.

The expressions A and B have type int [5][5], or "5-element array of 5-element array of int". By the rule above, both expressions "decay" to expressions of type "pointer to 5-element array of int", or int (*)[5]. If we initialize the array with A and B instead of &A and &B, then we need an array of pointers to 5-element arrays of int, or

int (*C[2])[5] = { A, B };

Okay, that's still pretty eye-stabby, but that's as clean as this is going to get without typedefs.

So how do we access elements of A and B through C?

Remember that the array subscript operation a[i] is defined as *(a + i); that is, given a base address a, offset i elements (not bytes)1 from that address and dereference the result. This means that

*a == *(a + 0) == a[0]

Thus,

*C[i] == *(C[i] + 0) == C[i][0]

Putting this all together:

C[0] == A                      // int [5][5], decays to int (*)[5]
C[1] == B                      // int [5][5], decays to int (*)[5]

*C[0] == C[0][0] == A[0]       // int [5], decays to int *
*C[1] == C[1][0] == B[0]       // int [5], decays to int *

C[0][i] == A[i]                // int [5], decays to int *
C[1][i] == B[i]                // int [5], decays to int *

C[0][i][j] == A[i][j]          // int
C[1][i][j] == B[i][j]          // int

We can index C as though it were a 3D array of int, which is a bit cleaner than (*C[i)[j][k].

This table may also be useful:

Expression        Type                "Decays" to       Value
----------        ----                -----------       -----
         A        int [5][5]           int (*)[5]       Address of A[0]
        &A        int (*)[5][5]                         Address of A
        *A        int [5]              int *            Value of A[0] (address of A[0][0])
      A[i]        int [5]              int *            Value of A[i] (address of A[i][0])
     &A[i]        int (*)[5]                            Address of A[i]
     *A[i]        int                                   Value of A[i][0]   
   A[i][j]        int                                   Value of A[i][j]   

Note that A, &A, A[0], &A[0], and &A[0][0] all yield the same value (the address of an array and the address of the first element of the array are always the same), but the types are different, as shown in the table above.


  1. Pointer arithmetic takes the size of the pointed-to type into account; if p contains the address of an int object, then p+1 yields the address of the next int object, which may be 2 to 4 bytes away.

John Bode
  • 119,563
  • 19
  • 122
  • 198
9

A common misconception among C beginners is that they just assume pointers and arrays are equivalent. That's completely wrong.

Confusion comes to beginners when they see the code like

int a1[] = {1,2,3,4,5};
int *p1 = a1;            // Beginners intuition: If 'p1' is a pointer and 'a1' can be assigned
                         // to it then arrays are pointers and pointers are arrays.

p1[1] = 0;               // Oh! I was right
a1[3] = 0;               // Bruce Wayne is the Batman! Yeah.

Now, it is verified by the beginners that arrays are pointers and pointers are arrays so they do such experiments:

int a2[][5] = {{0}};
int **p2 = a2;

And then a warning pops up about incompatible pointer assignment then they think: "Oh my God! Why has this array become Harvey Dent?".

Some even goes to one step ahead

int a3[][5][10] = {{{0}}};
int ***p3 = a3;             // "?"

and then Riddler comes to their nightmare of array-pointer equivalence.

enter image description here

Always remember that arrays are not pointers and vice-versa. An array is a data type and a pointer is another data type (which is not array type). This has been addressed several years ago in the C-FAQ:

Saying that arrays and pointers are "equivalent" means neither that they are identical nor even interchangeable. What it means is that array and pointer arithmetic is defined such that a pointer can be conveniently used to access an array or to simulate an array. In other words, as Wayne Throop has put it, it's "pointer arithmetic and array indexing [that] are equivalent in C, pointers and arrays are different.")

Now always remember few important rules for array to avoid this kind of confusion:

  • Arrays are not pointers. Pointers are not arrays.
  • Arrays are converted to pointer to their first element when used in an expression except when an operand of sizeof and & operator.
  • It's the pointer arithmetic and array indexing that are same.
  • Pointers and arrays are different.
  • Did I say "pointers are not arrays and vice-versa".

Now you have the rules, you can conclude that in

int a1[] = {1,2,3,4,5};
int *p1 = a1;

a1 is an array and in the declaration int *p1 = a1; it converted to pointer to its first element. Its elements are of type int then pointer to its first element would be of type int * which is compatible to p1.

In

int a2[][5] = {{0}};
int **p2 = a2;

a2 is an array and in int **p2 = a2; it decays to pointer to its first element. Its elements are of type int[5] (a 2D array is an array of 1D arrays), so a pointer to its first element would be of type int(*)[5] (pointer to array) which is incompatible with type int **. It should be

int (*p2)[5] = a2;

Similarly for

int a3[][5][10] = {{{0}}};
int ***p3 = a3;

elements of a3 is of type int [5][10] and pointer to its first element would be of type int (*)[5][10], but p3 is of int *** type, so to make them compatible, it should be

int (*p3)[5][10] = a3;

Now coming to your snippet

int A[5][5];
int B[5][5];
int*** C = {&A, &B};

&A and &B are of type int(*)[5][5]. C is of type int***, it's not an array. Since you want to make C to hold the address of both the arrays A and B, you need to declare C as an array of two int(*)[5][5] type elements. This should be done as

int (*C[2])[5][5] = {&A, &B};

However, if I dynamically allocate A and B it works just fine. Why is this?

In that case you must have declared A and B as int **. In this case both are pointers, not arrays. C is of type int ***, so it can hold an address of int** type data. Note that in this case the declaration int*** C = {&A, &B}; should be

  int*** C = &A;

In case of int*** C = {&A, &B};, the behavior of program would be either undefined or implementation defined.

C11: 5.1.1.3 (P1):

A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined

Read this post for further explanation.

Community
  • 1
  • 1
haccks
  • 104,019
  • 25
  • 176
  • 264
  • @GauravJain; Compile that code with C99 and you will get [warnings](https://ideone.com/1RX3j4). – haccks Jun 13 '16 at 18:54
  • ok... but how come did it work? what coincidence happened when i compiled it with gcc? – Flying disk Jun 14 '16 at 03:25
  • @GauravJain; Both of them are compiled with GCC the only difference is that you are not using C99 flag and therefore your's code is compiled in C89 mode by default. This older version of C may not produce any warning. – haccks Jun 14 '16 at 04:37
8

Arrays are not the same thing as multi-dimensional pointers in C. The name of the array gets interpreted as the address of the buffer that contains it in most cases, regardless of how you index it. If A is declared as int A[5][5], then A will usually mean the address of the first element, i.e., it is interpreted effectively as an int * (actually int *[5]), not an int ** at all. The computation of the address just happens to require two elements: A[x][y] = A + x + 5 * y. This is a convenience for doing A[x + 5 * y], it does not promote A to multidimensional buffer.

If you want multi-dimensional pointers in C, you can do that too. The syntax would be very similar, but it requires a bit more set up. There are a couple of common ways of doing it.

With a single buffer:

int **A = malloc(5 * sizeof(int *));
A[0] = malloc(5 * 5 * sizeof(int));
int i;
for(i = 1; i < 5; i++) {
    A[i] = A[0] + 5 * i;
}

With a separate buffer for each row:

int **A = malloc(5 * sizeof(int *));
int i;
for(i = 0; i < 5; i++) {
    A[i] = malloc(5 * sizeof(int));
}
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • Ok, i understand now. It explains everything :) Thanks – Viridya May 20 '16 at 14:12
  • 3
    Arrays are not ***at all*** the same thing as pointers, multi-dimensional or otherwise. Moreover, the name of an array designates the array, not any pointer. It is important not to confuse that with the fact that array values *decay* to pointers in most (but not all) contexts. – John Bollinger May 20 '16 at 14:17
  • 3
    Additionally, given `int A[5][5]`, when `A` does decay to a pointer, that pointer's type is `int (*)[5]`, not `int *`. – John Bollinger May 20 '16 at 14:19
  • @JohnBollinger, I fixed the first incorrect implication. – Mad Physicist May 20 '16 at 14:23
  • @JohnBollinger. I believe that I have addressed both of your comments now. – Mad Physicist May 20 '16 at 14:25
  • 1
    Don't allocate data like this when you want 2D arrays, it is simply bad practice with lots of drawbacks and no benefits. The only reason why you would want to make constructs like this is when _each dimension needs an individual size_. That is not the case here. – Lundin May 20 '16 at 14:26
  • @Lundin. For the second case, that is partially true. Also, when you can not find a chunk large enough to hold the entire thing in memory. – Mad Physicist May 20 '16 at 14:28
  • 2
    `(actually int *[5])` is not correct. This is an array of pointers not a pointer to an array. You calculation of the offsets is not correct. `A[x + 5 * y]` does not yield the same as `A[x][y]` – 2501 May 20 '16 at 16:38
  • 2
    This answer doesn't answer the question as well as misleading as pointed by @JohnBollinger and @ 2501. In addition: *If `A` is declared as `int A[5][5]`, then `A` will usually mean the address of the first element*: No. It always means `A` is an *array of 5 arrays of 5 `int`s*. Nothing more, nothing less. – haccks May 20 '16 at 20:28
7

You are being confused by the equivalence of arrays and pointers.

When you declare an array like A[5][5], because you have declared both dimensions, C will allocate memory for 25 objects contiguously. That is, memory will be allocated like this:

A00, A01, ... A04, A10, A11, ..., A14, A20, ..., A24, ...

The resulting object, A, is a pointer to the start of this block of memory. It is of type int *, not int **.

If you want a vector of pointers to arrays, you want to declare your variables as:

int   *A[5], *B[5];

That would give you:

A0, A1, A2, A3, A4

all of type int*, which you would have to fill using malloc() or whatever.

Alternatively, you could declare C as int **C.

aghast
  • 14,785
  • 3
  • 24
  • 56
  • The problem with `int **C` from the OP's standpoint is that it loses the 2D indexing information. Great answer regardless. – Mad Physicist May 20 '16 at 14:18
  • 4
    There is no equivalence between arrays and pointers. They are associated, but not at all the same thing. Also, given `int A[5][5]`, the pointer type to which `A` decays is `int (*)[5]`, not `int *`. – John Bollinger May 20 '16 at 14:21
  • You may consider rephrasing `vector of pointers to arrays` because of in subsequent statement you have two arrays of pointers. Just not giving intended effect. – sjsam May 20 '16 at 14:35
  • 1
    Sorry to say but your answer is misleading. You said: *The resulting object, `A`, is a pointer to the start of this block of memory. It is of type `int *`, not `int **`.*. It's completely wrong. The object `A` is of an array type. It will convert to pointer to it's first element in some cases. Type of `A` is `int[5][5]`. After conversion it has the type `int (*)[5]`. Again: *If you want a vector of pointers to arrays, you want to declare your variables as: `int *A[5], *B[5];`*: No. It will declare *arrays of pointers* and not a vector of *pointers to arrays*. – haccks May 20 '16 at 20:18
  • `int *A[5], *B[5];` Why are you saying those are vectors (which has no meaning in C, but let's assume you mean arrays) of pointers to arrays, when they are obviously not? – 2501 May 22 '16 at 16:43
7

Although arrays and pointers are closely associated, they are not at all the same thing. People are sometimes confused about this because in most contexts, array values decay to pointers, and because array notation can be used in function prototypes to declare parameters that are in fact pointers. Additionally, what many people think of as array indexing notation really performs a combination of pointer arithmetic and dereferencing, so that it works equally well for pointer values and for array values (because array values decay to pointers).

Given the declaration

int A[5][5];

Variable A designates an array of five arrays of five int. This decays, where it decays, to a pointer of type int (*)[5] -- that is, a pointer to an array of 5 int. A pointer to the whole multi-dimensional array, on the other hand, has type int (*)[5][5] (pointer to array of 5 arrays of 5 int), which is altogether different from int *** (pointer to pointer to pointer to int). If you want to declare a pointer to a multi-dimensional array such as these then you could do it like this:

int A[5][5];
int B[5][5];
int (*C)[5][5] = &A;

If you want to declare an array of such pointers then you could do this:

int (*D[2])[5][5] = { &A, &B };

Added:

These distinctions come into play in various ways, some of the more important being the contexts where array values do not decays to pointers, and contexts related to those. One of the most significant of these is when a value is the operand of the sizeof operator. Given the above declarations, all of the following relational expressions evaluate to 1 (true):

sizeof(A)       == 5 * 5 * sizeof(int)
sizeof(A[0])    == 5 * sizeof(int)
sizeof(A[0][4]) == sizeof(int)
sizeof(D[1])    == sizeof(C)
sizeof(*C)      == sizeof(A)

Additionally, it is likely, but not guaranteed, that these relational expressions evaluate to 1:

sizeof(C)       == sizeof(void *)
sizeof(D)       == 2 * sizeof(void *)

This is fundamental to how array indexing works, and essential to understand when you are allocating memory.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
3

Either you should declare the third array like

int A[5][5];
int B[5][5];
int ( *C[] )[N][N] = { &A, &B };

that is as an array of pointers to two-dimensional arrays.

For example

#include <stdio.h>

#define N   5

void output( int ( *a )[N][N] )
{
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ ) printf( "%2d ", ( *a )[i][j] );
        printf( "\n" );
    }
}

int main( void )
{
    int A[N][N] =
    {
        {  1,  2,  3,  4,  5 },
        {  6,  7,  8,  9, 10 },
        { 11, 12, 13, 14, 15 },
        { 16, 17, 18, 19, 20 },
        { 21, 22, 23, 24, 25 }
    };
    int B[N][N] =
    {
        { 25, 24, 23, 22, 21 },
        { 20, 19, 18, 17, 16 },
        { 15, 14, 13, 12, 11 },
        { 10,  9,  8,  7,  6 },
        {  5,  4,  3,  2,  1 }
    };

/*
    typedef int ( *T )[N][N];
    T C[] = { &A, &B };
*/

    int ( *C[] )[N][N] = { &A, &B };

    output( C[0] );
    printf( "\n" );

    output( C[1] );
    printf( "\n" );
}        

The program output is

 1  2  3  4  5 
 6  7  8  9 10 
11 12 13 14 15 
16 17 18 19 20 
21 22 23 24 25 

25 24 23 22 21 
20 19 18 17 16 
15 14 13 12 11 
10  9  8  7  6 
 5  4  3  2  1 

or like

int A[5][5];
int B[5][5];
int ( *C[] )[N] = { A, B };

that is as an array of pointers to the first elements of two-dimensional arrays.

For example

#include <stdio.h>

#define N   5

void output( int ( *a )[N] )
{
    for ( size_t i = 0; i < N; i++ )
    {
        for ( size_t j = 0; j < N; j++ ) printf( "%2d ", a[i][j] );
        printf( "\n" );
    }
}

int main( void )
{
    int A[N][N] =
    {
        {  1,  2,  3,  4,  5 },
        {  6,  7,  8,  9, 10 },
        { 11, 12, 13, 14, 15 },
        { 16, 17, 18, 19, 20 },
        { 21, 22, 23, 24, 25 }
    };
    int B[N][N] =
    {
        { 25, 24, 23, 22, 21 },
        { 20, 19, 18, 17, 16 },
        { 15, 14, 13, 12, 11 },
        { 10,  9,  8,  7,  6 },
        {  5,  4,  3,  2,  1 }
    };

/*
    typedef int ( *T )[N];
    T C[] = { A, B };
*/

    int ( *C[] )[N] = { A, B };

    output( C[0] );
    printf( "\n" );

    output( C[1] );
    printf( "\n" );
}        

The program output is the same as above

 1  2  3  4  5 
 6  7  8  9 10 
11 12 13 14 15 
16 17 18 19 20 
21 22 23 24 25 

25 24 23 22 21 
20 19 18 17 16 
15 14 13 12 11 
10  9  8  7  6 
 5  4  3  2  1 

depending on how you are going to use the third array.

Using typedefs (shown in the demonstrative program as commented) ssimplifies the arrays' definitions.

As for this declaration

int*** C = {&A, &B};

then in the left side there is declared a pointer of type int *** that is a scalar object while in the right side there is a list of initializers that have different type int ( * )[N][N].

So the compiler issues a message.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
-1

I am a great believer in using typedef:

#define SIZE 5

typedef int  OneD[SIZE]; // OneD is a one-dimensional array of ints
typedef OneD TwoD[SIZE]; // TwoD is a one-dimensional array of OneD's
                         // So it's a two-dimensional array of ints!

TwoD a;
TwoD b;

TwoD *c[] = { &a, &b, 0 }; // c is a one-dimensional array of pointers to TwoD's
                           // That does NOT make it a three-dimensional array!

int main() {
    for (int i = 0; c[i] != 0; ++i) { // Test contents of c to not go too far!
        for (int j = 0; j < SIZE; ++j) {
            for (int k = 0; k < SIZE; ++k) {
//              c[i][j][k] = 0;    // Error! This proves it's not a 3D array!
                (*c[i])[j][k] = 0; // You need to dereference the entry in c first
            } // for
        } // for
    } // for
    return 0;
} // main()
John Burger
  • 3,662
  • 1
  • 13
  • 23