why is int ** x not the same as int x[][]?

天大地大妈咪最大 提交于 2021-02-11 09:39:07

问题


This may be a dumb question but I don't understand why do I get this error :

void func (int ** array) { }
 
int main (void) {
    int array[5][5] = { };
    func(array);
}


 warning: passing argument 1 of 'func' from incompatible pointer type [-Wincompatible-pointer-types]
      func(array);
           ^~~~~
    note: expected 'int **' but argument is of type 'int (*)[5]'
     void func (int ** array) { }
            ~~~~~~~^~~~~

回答1:


Consider a function void foo(int **p). The p it is given contains some address. At that address, there is an int *. If foo loads the data from that address and uses it as an address for a load, it will load an int.

Now consider an array int a[5][5]. Suppose we take the address of this array (or, somewhat equivalent, the address of its first element) and pass it to foo. Then foo has a p that contains the address of a. At that address, there is no int *. At that address, there are 25 int values, arranged in 5 groups of 5.

So, an int [5][5] is not an int **. And the address of its first element is not an int **, nor is its first element an int *.

That is why you cannot pass an int [][] in place of an int **.

In contrast, consider a function void foo(int *p). The p this function is given contains some address. At that address, there is an int. If we have an array int a[5], and we pass the address of its first element to foo, then foo receives a p that contains the address of an int. Thus, an int [] that has been converted to the address of its first element is an int *.

These automatic array conversions work only on the first dimension of an array. If you have an int a[5][6][7], the automatic conversion to its first element gives you a pointer to an int [6][7], which is an int (*)[6][7]. The pointer points to groups of integers in memory. It does not point to a pointer.

Automatic conversion of an array to a pointer does not change what is actually in memory. So it cannot change the type of the thing that is pointed to: We can point to the int in an int [][], but we cannot point to pointers to them because there are no such pointers in memory.




回答2:


The int ** type means a double pointer to an int, but you have an array of pointers here. Your function declaration should therefore be

    void func (int (*array)[]) { }

The internal functionality of a double pointer to an int can be represented as follows:

+-------+     +------+     +-----+
| int** | --> | int* | --> | int |
+-------+     +------+     +-----+

Each square should represent a memory location, with the type in it. In contrast, the representation of int (*array)[] would be the following:

                      +-------------+
array ->  array[0] -> | array[0][0] |
                      +-------------+
                      | array[0][1] |
                      +-------------+
                      |     ...     |
                      +-------------+
          array[1] -> | array[1][0] |
                      +-------------+
                            ...      

Each square shows how the corresponding value can be accessed. Note that only the values on the far right are in memory. Pointers are not required here, since the address of an array element can be calculated directly. Therefore

printf("%ld\n", sizeof(array)/sizeof(int)); // Outputs 25

indicates that memory space has been reserved for 25 values. So internally it's the same as declaring int array[25], but with a different type and and access to array elements.




回答3:


If this is a part of a declaration:

type **var;

would mean by a pointer to a pointer, that is, a variable that can hold address of another variable, which is also a pointer to the var of the type type.

But not with this case:

type *var[N];

This one states to the compiler that this is a pointer to the variable var of N contained array elements.

As a type of dereference:

**var is double dereference. Only if variable is an address of an address, the resulting expression will be the lvalue at the address stored in *var of type type.


You're trying to pass a pointer to a pointer type to pointer to an array type accepted by the function func(). That's the reason of error.




回答4:


Some notes to clarify some things:

  • Even if pointers and arrays can often be used the same way and seem to be the same they are actually different.
  • An array specifies storage (ignoring incomplete array like e.g. a[] here but they still need storage defined somewhere else).
  • A pointer does not define storage (it is pointing to).
  • An array name in an expression is treated by the compiler as pointer to the first element.
  • The same for an array name in the declaration of a function parameter.
  • A subscript is always equivalent to an offset to a pointer.
  • On argument passing to function an implicit casting may happen (keep that in mind) but sometimes this is failing and that's what the compiler is saying (int ** != int *[]). You could do explicit casting but that would be nasty and requires careful programming. Better fix function prototype.

With int **array you just define a handle (pointer to a pointer) to an int. But as the compiler says you are passing int *array[5] which is an array of 5 pointers to int. You need to change the function declaration to pass a pointer to an array of int. Because otherwise func would not be able to access elements of that array you passed.

That means: When "building" the declaration of the tyoe you have at first stage an array int array[] and that needs to be changed at second stage to be a pointer to that array: int ... *array ... [] (can't explain better without drawing) which will finally be int (*array)[].

If it helps to clarify then use typedef:

typedef int (*ah)[];
void func (ah x) { }

Or (better):

typedef int a[];
void func (a *x) { }

Here you can clearly see which type of pointer x actually is.




回答5:


Except when it is the operand of the sizeof, _Alignof, or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type “N-element array of T” will be converted (“decay”) an expression of type “pointer to T” and the value of the expression will be the first element of the array.

When you pass the expression array to func, it “decays” from type “5-element array of 5-element array int” to “pointer to 5-element array of int” (int (*)[5]). So your function prototype needs to be

void func( int (*array)[5] )

You would index into this the same as any 2D array.

Edit

From the comments:

But what is the difference at a low level ?

The structure of the memory is different. A true 2D array has all its rows laid out contiguously and all rows have the same number of elements. Given the definition

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

it’s laid out in memory like so (addresses are just for illustration’s sake):

   +–––+
   | 1 | a[0][0] (0x8000)
   + – + 
   | 2 | a[0][1] (0x8004)
   +–––+
   | 3 | a[1][0] (0x8008)
   + - +
   | 4 | a[1][1] (0x800C)
   +–––+

Pointer arithmetic takes the size of the pointed-to type into account - given a pointer p the expression p + 1 yields the address of the next object of the pointed-to type, not the next byte.

This is how array subscripting works - the expression a[i] is exactly equivalent to *(a + i). Given a starting address a, offset i elements from that address and dereference the result. In the case where a refers to an array, it “decays” to a pointer to the first element.

When we write

x = a[1][1];

the expression a “decays” to type int (*)[2] and its value is the address of the first element of a (0x8000). a[1] is evaluated as *(a + 1). a + 1 yields the address of the second 2-element array of int (0x8008) following a.

Since the expression a has type int (*)[2], the expression *a has type int [2]. Since *a == *(a + 0), the expression a[0] has type int [2], and by extension any a[i] has type int [2].

Since the expression a[1] has type int [2], it decays to type int *, and its value is the address of the first element of a[1] (0x8008). a[1][1] is evaluated as *(a[1] + 1). a[1] + 1 yields the address of the second int object following a[1].

An int ** OTOH is typically used to implement a structure that looks like this:

   int **            int *               int
   +–––+             +–––+               +–––+
a: | +-+–––––> a[0]: | +-+––––> a[0][0]: | 1 | 
   +–––+             +–––+               +–––+
               a[1]: | +–+––+   a[0][1]: | 2 | 
                     +-––+  |            +–––+
                            |             ...
                            |            +–––+
                            +–> a[1][0]: | 3 | 
                                         +–––+
                                a[1][1]: | 4 | 
                                         +–––+

The "rows" of the array are not contiguous in memory, and they don’t even have to be the same length. Such a structure is typically allocated as

int **a = malloc( sizeof *a * 2 );
if ( a )
{
  for ( int i = 0; i < 2; i++ )
  {
    a[i] = malloc( sizeof *a[i] * 2 );
    if ( a[i] )
    {
      a[i][0] = 2 * i;
      a[i][1] = 2 * i + 1;
    }
  }
}

In this case a is explicitly a pointer and each a[i] is explicitly a pointer. There’s no implicit conversion from an array type to a pointer type. You can index into this structure using the same notation as for a 2D array, but the underlying structure is completely different.



来源:https://stackoverflow.com/questions/62731862/why-is-int-x-not-the-same-as-int-x

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!