Aren的[] []和(* a)[]等价作为函数参数?

时间:2017-06-20 14:28:37

标签: c arrays gcc language-lawyer function-parameter

功能原型

void foo(int n, int a[][]);

给出了关于不完整类型的错误

void foo(int n, int (*a)[]);  

编译。根据衰退规则int a[][]在这种情况下相当于int (*a)[],因此int (*a)[]也应该提供有关不完整类型的错误,但GCC似乎接受它。有什么我想念的吗? 这可能是一个海湾合作委员会的错误,但我没有找到与之相关的任何内容。

3 个答案:

答案 0 :(得分:9)

不,它们不等同于函数参数。它们与foobar

中的参数声明完全相同
struct S;
void foo(struct S* s); // OK
void bar(struct S a[]); // ERROR: incomplete type is not allowed

不等同。

C不允许将不完整的类型作为数组元素(参见C 1999 6.7.5.2/1:“[...]元素类型不应是不完整的或函数类型。[...]”)和此限制适用于数组参数声明的方式与应用于任何其他数组声明的方式相同。即使数组类型的参数稍后将被隐式地调整到指针类型,C也不会对函数参数列表中的数组声明提供特殊处理。换句话说,在上述调整之前检查数组参数声明的有效性

你的int a[][]是一回事:试图声明一个类型为int []的数组的数组,这是一个不完整的类型。与此同时,int (*a)[]完全合法 - 指向不完整类型的指针没什么不寻常的。

作为旁注,C ++“修复”了这个问题,允许在参数声明中使用不完整类型的数组。但是,原始C ++仍然禁止int a[][]参数,int (&a)[]参数甚至int (*a)[]参数。这应该是后来在C ++ 17(http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393

中修复/允许的

答案 1 :(得分:6)

在不需要知道大小的上下文中允许使用不完整类型。

有了这个声明:

int a[][]

即使作为函数参数也是无效的,因为需要一个数组维的大小才能知道如何在第二维上执行指针运算。

然而这是有效的:

int (*a)[];

因为为了使用指向它的指针,不需要知道数组的大小。

C standard的第6.2.7节给出了这样一个声明的例子:

  

5 示例给定以下两个文件范围声明:

int f(int (*)(), double (*)[3]);
int f(int (*)(char *), double (*)[]);
     

该函数的最终复合类型为:

int f(int (*)(char *), double (*)[3]);

此示例显示类型double (*)[3]的声明,该声明与类型为double (*)[]的声明兼容

然而,由于缺少尺寸,您不能像2D阵列那样直接使用它。以下是一些示例。如果您尝试这样做:

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

编译器(如预期的那样)告诉你:

error: invalid use of array with unspecified bounds
         printf("a[%d][%d]=%d\n",i,j,a[i][j]);
         ^

你可以利用这样一个事实来解决这个问题:在大多数情况下,即使是不确定大小的数组也会衰减到指针:

#include <stdio.h>

void foo(int n, int (*a)[])
{
    int i,j;
    for (i=0;i<n;i++) {
        // dereference "a", type is int[], which decays to int *
        // now manually add "n" ints times the row
        int *b = *a + (n*i);
        for (j=0;j<n;j++) {
            printf("a[%d][%d]=%d\n",i,j,b[j]);
        }
    }
}

int main()
{
    int a[2][2] = { {4,5},{6,7} };
    foo(2,a);

    return 0;
}

使用以下输出编译清理:

a[0][0]=4
a[0][1]=5
a[1][0]=6
a[1][1]=7

即使在函数之外,也可以使用int (*)[]语法:

#include <stdio.h>

int main()
{
    int a[2][2] = { {4,5},{6,7} };
    int i,j,n=2;
    int (*aa)[];
    // "a" decays from int[2][2] to int (*)[2], assigned to int (*)[]
    aa = a;
    for (i=0;i<n;i++) {
        int *b = *aa + (n*i);
        for (j=0;j<n;j++) {
            printf("a[%d][%d]=%d\n",i,j,b[j]);
        }
    }

    return 0;
}

答案 2 :(得分:3)

修改

阅读了标准的所有相关部分,C11 6.7.6.2和6.7.6.3,我认为这是编译器错误/不符合。它显然归结为文本委员会潜入了关于数组分隔符的段落中间。 6.7.6.2/1强调我的:

  

除了可选的类型限定符和关键字static之外,[   和]可以划定表达式或*。如果他们划定表达式   (指定数组的大小),表达式应具有   整数类型。如果表达式是常量表达式,它应该是   值大于零。 T 元素类型不应为   不完整或函数类型。可选的类型限定符和   关键字static只出现在函数的声明中   具有数组类型的参数,然后仅在最外面的数组中   类型推导。

现在这当然写得非常糟糕,基本上它说

  “外围特征兴趣不大,周边特征不大   感兴趣,没有兴趣的外围特征,在这里出现的蓝色元素类型规范与本段的其余部分无关,外围特征没什么兴趣,外围特征   没什么兴趣,......“

因此很容易误解,欺骗了我。

意味着int a[][]无论在何处声明都始终不正确,因为数组不能是不完整类型的数组。

但是,我在下面的原始答案提出了一些关于阵列衰减应该在编译器决定类型是否不完整之前或之后完成的有效问题。

仅给出具体案例void foo(int n, int a[][]);,这是一个函数声明。这不是一个定义。

C11 6.7.6.3/12

  

如果函数声明符不是该定义的一部分   功能,参数可能有不完整的类型

首先,参数 允许在函数声明中具有不完整的类型。标准很清楚。这就是为什么这样的代码编译得很好的原因:

struct s; // incomplete type
void foo(int n, struct s a); // just fine, incomplete type is allowed in the declaration

此外:

C11 6.7.6.3/4

  

调整后,作为该函数定义一部分的函数声明符中参数类型列表中的参数不应具有不完整的类型。

调整后在这里非常重要。

意味着在将int a[][]调整为int (*a)[]后,参数不应具有不完整的类型。它没有,它是一个指向不完整类型的指针,它总是被允许并完全正常。

不允许编译器首先将int a[][]评估为不完整数组的不完整数组,然后再调整它(如果发现该类型不完整)。这将直接违反6.7.6.3/4。