"int (*)[]" 是否会在函数参数中衰减为 "int **"?

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

我今天早些时候在 programmers.stackexchange 上发布了这个 question。我一直假设 int (*)[] 在函数参数中 不会 衰减到 int ** 但我对我的问题有多个回复表明它确实如此。

我在我的函数参数中大量使用了 int (*)[],但现在我真的很困惑。

当我使用gcc -std=c99 -pedantic -Wall

编译这个函数时
void function(int (*a)[])
{
    sizeof(*a);
}

我收到此错误消息:

c99 -Wall -pedantic -c main.c -o main.o
main.c: In function ‘function’:
main.c:3:11: error: invalid application of ‘sizeof’ to incomplete type ‘int[]’ 
make: *** [main.o] Error 1

这表明 *a 的类型为 int [] 而不是 int *.

有人可以解释一下 int (*)[] 之类的东西是否会在函数参数中衰减为 int ** 并给我一些参考(也许来自标准文档)来证明为什么会这样。

只有数组类型在传递给函数时转换为指向其第一个元素的指针。 a指向int数组的指针类型,即是指针类型,因此没有转换 .

对于原型

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

编译器将其解释为

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

那是因为a[]是数组类型。 int a[][10] 永远不会转换为 int **a。也就是说,answer 中的第二段是错误的且具有误导性。

作为函数参数,int *a[]等同于int **这是因为aarray类型。

int (*)[] 是指向 int.

数组的指针

在你的例子中,*a可以衰减到int*。但是 sizeof(*a) 不会衰减;它本质上是 sizeof(int[]),这是无效的。

a根本不能衰减(是指针)

N1256 §6.7.5.3/p7-8

7 A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

8 A declaration of a parameter as ‘‘function returning type’’ shall be adjusted to ‘‘pointer to function returning type’’, as in 6.3.2.1.

int (*)[] 是 "pointer to array of int"。是"array of type"吗?不,它是一个指针。是"function returning type"吗?不,它是一个指针。因此它没有得到调整。

int (*a)[] 的情况下,sizeof *a 不起作用的原因之一是:数组没有元素计数。没有元素计数,无法计算大小。

因此,a 上的任何指针算法都将不起作用,因为它是根据对象的大小定义的。由于数组的大小是不确定的,因此不能对指针本身使用指针算法。数组符号是根据指针算术定义的,因此 sizeof a[0][0](或任何涉及 a[n] 的表达式将不起作用,而 sizeof (*a)[0] 将起作用。

这实际上意味着您可以用指针做很少的事情。唯一允许的是:

  • 使用一元 * 运算符取消引用指针
  • 将指针传递给另一个函数(函数参数的类型必须是数组的数组或指向数组的指针)
  • 获取指针的大小(以及对齐方式或类型,如果您的编译器支持其中一个或两者)
  • 将指针分配给兼容类型

如果您的编译器支持可变长度数组 (VLA),并且您知道大小,则只需在函数体的开头添加一行即可解决此问题,如

void
foo (int (*a0)[], size_t m, size_t n)
{
  int (*a)[n] = a0;
  ...
}

没有 VLA,您必须采取其他措施。

值得注意的是,动态分配不是 int (*)[] 的一个因素。数组数组衰减为指向数组的指针(就像我们这里的那样),因此在将它们传递给函数时它们可以互换(sizeof 和任何 _Alignoftypeof 关键字是运算符,不是函数)。这意味着指向的数组必须是静态分配的:一旦数组衰减为指针,就不会再发生衰减,所以你不能说指向数组 (int (*)[]) 的指针与指向的指针相同一个指针(int **)。否则,您的编译器会很乐意让您将 int [3][3] 传递给接受 int ** 的函数,而不是想要 int (*)[]int (*)[n]int [][n]int [][n] 形式的参数int [m][n].

因此,即使您的编译器不支持 VLA,您也可以利用静态分配数组将其所有元素组合在一起这一事实:

void foo (int (*a0)[], size_t m, size_t n)
{
  int *a = *a0;
  size_t i, j;

  for (i = 0; i < m; i++)
    {
      for (j = 0; j < n; j++)
        {
          // Do something with `a[i * n + j]`, which is `a0[i][j]`.
        }
    }
  ...
}

动态分配的一维数组作为二维数组使用,具有相同的属性,所以这仍然有效。只有当第二个维度是动态分配的时候,这意味着像 for (i = 0; i < m; i++) a[i] = malloc (n * sizeof *a[i]); 这样的循环来单独分配每个子数组,这个原则就不起作用了。这是因为你有一个指针数组(int *[],或数组衰减后的 int **),它指向内存中另一个位置的数组的第一个元素,而不是数组的数组,它将所有项目放在一起。

所以:

  • 不行,int (*p)[]int **q不能用同样的方法。 p 是指向数组的指针,这意味着所有项目都从存储在 p 中的地址开始组合在一起。 q 是一个指向指针的指针,这意味着项目可能分散在不同的地址,存储在 q[0], q[1], ..., q[m - 1].

  • sizeof *p 不起作用,因为 p 指向一个元素数量未知的数组。编译器无法计算每个元素的大小,所以对 p 本身的操作非常有限。