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

时间:2015-01-10 16:46:39

标签: c arrays function pointers language-lawyer

我今天早些时候在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 **并给我一些参考(可能来自标准文档),这证明了为什么会这样。

4 个答案:

答案 0 :(得分:18)

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

原型

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

编译器将其解释为

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

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

作为函数参数,int *a[]相当于int **,因为a属于数组类型。

答案 1 :(得分:7)

int (*)[]是指向int数组的指针。

在您的示例中,*a 可以衰减到int*。但是sizeof(*a)并没有腐烂;它本质上是sizeof(int[]),无效。

a根本无法衰退(它是一个指针)。

答案 2 :(得分:7)

N1256§6.7.5.3/ p7-8

  

7参数声明为' type ''的数组应为   调整为''合格指针    type '',其中类型限定符(如果有)是在数组类型派生的[]中指定的类型限定符。如果是关键字   static也出现在数组类型的[]内   派生,然后对每个函数的调用,值的   相应的实际参数应提供对第一个的访问权限   数组的元素,其元素至少与指定的元素一样多   大小表达。

     

8参数声明为''函数返回 type ''   被调整为''指向函数返回类型'的指针,如   6.3.2.1。

int (*)[]是“指向int数组的指针”。它是“ type ”的数组吗?不,这是一个指针。它是“函数返回类型”吗?不,这是一个指针。因此它没有得到调整。

答案 3 :(得分:2)

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 (*)[])与a相同指向指针(int **)的指针。否则,您的编译器会很乐意让您将int [3][3]传递给接受int **的函数,而不是想要int (*)[]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本身的操作非常有限。