难道a[][]和(*a)[]不是等价于函数参数吗?

时间:2020-12-21 22:42:21

Function prototype

函数原型

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

gives error about incomplete type while

给出不完整类型的错误。

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

compiles. As per the decay rule int a[][] is equivalent to int (*a)[] in this case and therefore int (*a)[] should also give an error about an incomplete type but GCC seems to accept it. Is there anything I am missing? This might be a GCC bug but I didn't find anything related to it.

编译。根据衰减规则int a[][]在这种情况下等价于int (*a)[],因此int (*a)[]也应该给出一个关于不完整类型的错误,但GCC似乎接受它。有什么我遗漏的吗?这可能是一个GCC bug,但是我没有找到任何相关的东西。

3 个解决方案

#1


8  

No, they are not equivalent as function parameters. They are not equivalent in exactly the same way as parameter declarations in foo and bar

不,它们不等于函数参数。它们并不完全等价于foo和bar中的参数声明

struct S;
void foo(struct S* s); // OK
void bar(struct S a[]); // ERROR: incomplete type is not allowed

are not equivalent.

并不是等价的。

C does not allow incomplete types as array elements (see 6.7.5.2/1: "[...] The element type shall not be an incomplete or function type. [...]") and this restriction applies to array parameter declarations the same way as it applies to any other array declarations. Even though parameters of array type will be later implicitly adjusted to pointer type, C simply provides no special treatment for array declarations in function parameter lists. In other words, array parameter declarations are checked for validity before the aforementioned adjustment.

C不允许不完整的类型作为数组元素(参见6.7.5.2/1:“[…]单元类型不应是不完整的或函数类型。这个限制适用于数组参数声明,就像它适用于任何其他数组声明一样。即使以后数组类型的参数将隐式地调整为指针类型,C也不会对函数参数列表中的数组声明提供特殊处理。换句话说,在进行上述调整之前,要检查数组参数声明的有效性。

Your int a[][] is the same thing: an attempt to declare an array with elements of type int [], which is an incomplete type. Meanwhile, int (*a)[] is perfectly legal - there's nothing unusual about pointers to incomplete types.

您的int a[][]是一样的:尝试声明一个包含int[]类型元素的数组,这是一个不完整的类型。与此同时,int (*a)[]是完全合法的——指向不完整类型的指针没有什么特别之处。

As a side note, C++ "fixed" this issue, allowing arrays of incomplete type in parameter declarations. However, the original C++ still prohibits int a[][] parameters, int (&a)[] parameters and even int (*a)[] parameters. This was supposedly fixed/allowed later in C++17 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393)

作为附带说明,c++“修复”了这个问题,允许在参数声明中使用不完全类型的数组。但是,原始的c++仍然禁止int a[][]参数、int (&a)[]参数甚至int (*a)[]参数。这在稍后的c++ 17中被认为是可以修复/允许的(http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393)

#2


6  

An incomplete type is allowed in contexts where the size doesn't need to be known.

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

With this declaration:

这个声明:

int a[][]

It is invalid even as a function parameter because the size of one array dimension is needed to know how to perform pointer arithmetic on the second dimension.

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

This however is valid:

这不过是有效的:

int (*a)[];

Because the size of the array doesn't need to be known in order to use a pointer to it.

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

Section 6.2.7 of the C standard gives an example of a declaration like this:

C标准的第6.2.7节给出了这样一个声明的示例:

5 EXAMPLE Given the following two file scope declarations:

给出以下两个文件范围声明的示例:

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

The resulting composite type for the function is:

该函数的合成类型为:

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

This example shows a declaration of type double (*)[3] that is compatible with a declaration of type double (*)[]

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

You can't however directly use this like a 2D array because of the missing size. Here are some examples to illustrate. If you attempt to do this:

但是由于缺少尺寸,您不能像使用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]);
        }
    }
}

The compiler (as expected) tells you this:

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

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

You can get around this by taking advantage of the fact that an array, even of indeterminate size, decays to a pointer in most contexts:

你可以利用一个数组,即使是不确定的大小,在大多数情况下也会衰减成指针:

#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;
}

This compiles clean with the following output:

此编译干净,输出如下:

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

Even outside of a function, the int (*)[] syntax can be used:

即使在函数之外,也可以使用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;
}

#3


3  

EDIT

编辑

Having read through all the relevant parts of the standard, C11 6.7.6.2 and 6.7.6.3, I believe this is a compiler bug/non-conformance. it apparently boils down to the text that the committee sneaked into the middle of a paragraph concerning array delimiters. 6.7.6.2/1 emphasis mine:

在阅读了C11 6.7.6.2和6.7.6.3标准的所有相关部分后,我认为这是一个编译器错误/不符合。显然,这可以归结为委员会在一段关于数组分隔符的段落中偷偷地加入了文本。6.7.6.2/1我特别强调:

In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.

除了可选类型限定符和关键字静态外,[and]还可以分隔表达式或*。如果它们分隔表达式(指定数组的大小),则表达式应该具有整数类型。如果表达式是一个常量表达式,那么它的值应该大于零。元素类型不应是不完整的或函数类型。可选类型限定符和关键字静态只能出现在具有数组类型的函数参数的声明中,然后只能出现在最外层的数组类型派生中。

Now this is of course very poorly written, basically it says

这篇文章写得很糟糕

"peripheral feature of little interest, peripheral feature of little interest, peripheral feature of little interest, OUT OF THE BLUE HERE COMES SOME ARRAY ELEMENT TYPE SPECIFICATION NOT RELATED TO THE REST OF THIS PARAGRAPH, peripheral feature of little interest, peripheral feature of little interest,...."

“外围功能的小兴趣,外围功能的小兴趣,外围功能的小兴趣,突然来了一些数组元素类型规范与剩下的这一段,外围功能的小兴趣,外围功能的小兴趣,....”

So it is easy to misunderstand, fooled me.

所以很容易误解,愚弄我。

Meaning that int a[][] is always incorrect no matter where it is declared, since an array cannot be an array of incomplete type.

意思是,无论在哪里声明,int a[][]总是不正确的,因为数组不能是不完全类型的数组。

However, my original answer below raises some valid concerns regarding whether array decay should be done before or after the compiler decides if the type is incomplete or not.

但是,下面我的原始答案提出了一些合理的问题,即在编译器决定类型是否不完整之前或之后是否应该进行数组衰减。


Given the specific case void foo(int n, int a[][]); only, this is a function declaration. It is not a definition.

给定特定情况下为void foo(int n, int a[][]);只是,这是一个函数声明。它不是一个定义。

C11 6.7.6.3/12

C11 6.7.6.3/12

If the function declarator is not part of a definition of that function, parameters may have incomplete type

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

So first of all, parameters are allowed to have incomplete type in the function declaration. The standard is clear. Which is why code like this compiles just fine:

首先,参数在函数声明中可以有不完整的类型。的标准是明确的。这就是为什么像这样的代码编译得很好:

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

Furthermore:

此外:

C11 6.7.6.3/4

C11 6.7.6.3/4

After adjustment, the parameters in a parameter type list in a function declarator that is part of a definition of that function shall not have incomplete type.

在调整后,函数声明器中的参数类型列表中的参数是该函数定义的一部分,不具有不完全类型。

After adjustment is very important here.

在这里,调整之后是非常重要的。

Meaning that after adjusting int a[][] to int (*a)[], the parameter shall not have incomplete type. It does not, it is a pointer to incomplete type, which is always allowed and perfectly fine.

表示将int a[][][]调整为int (*a)[]后,参数不应具有不完备类型。它不是,它是一个指向不完整类型的指针,它总是允许的,而且非常好。

The compiler is not allowed to first evaluate int a[][] as an incomplete array of incomplete arrays, and then later adjust it (if it found that the type was not incomplete). This would directly violate 6.7.6.3/4.

编译器不允许首先将int a[][]作为不完整数组的不完整数组求值,然后再对其进行调整(如果发现类型不是不完整的)。这直接违反了6。7。3/4。

#1


8  

No, they are not equivalent as function parameters. They are not equivalent in exactly the same way as parameter declarations in foo and bar

不,它们不等于函数参数。它们并不完全等价于foo和bar中的参数声明

struct S;
void foo(struct S* s); // OK
void bar(struct S a[]); // ERROR: incomplete type is not allowed

are not equivalent.

并不是等价的。

C does not allow incomplete types as array elements (see 6.7.5.2/1: "[...] The element type shall not be an incomplete or function type. [...]") and this restriction applies to array parameter declarations the same way as it applies to any other array declarations. Even though parameters of array type will be later implicitly adjusted to pointer type, C simply provides no special treatment for array declarations in function parameter lists. In other words, array parameter declarations are checked for validity before the aforementioned adjustment.

C不允许不完整的类型作为数组元素(参见6.7.5.2/1:“[…]单元类型不应是不完整的或函数类型。这个限制适用于数组参数声明,就像它适用于任何其他数组声明一样。即使以后数组类型的参数将隐式地调整为指针类型,C也不会对函数参数列表中的数组声明提供特殊处理。换句话说,在进行上述调整之前,要检查数组参数声明的有效性。

Your int a[][] is the same thing: an attempt to declare an array with elements of type int [], which is an incomplete type. Meanwhile, int (*a)[] is perfectly legal - there's nothing unusual about pointers to incomplete types.

您的int a[][]是一样的:尝试声明一个包含int[]类型元素的数组,这是一个不完整的类型。与此同时,int (*a)[]是完全合法的——指向不完整类型的指针没有什么特别之处。

As a side note, C++ "fixed" this issue, allowing arrays of incomplete type in parameter declarations. However, the original C++ still prohibits int a[][] parameters, int (&a)[] parameters and even int (*a)[] parameters. This was supposedly fixed/allowed later in C++17 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393)

作为附带说明,c++“修复”了这个问题,允许在参数声明中使用不完全类型的数组。但是,原始的c++仍然禁止int a[][]参数、int (&a)[]参数甚至int (*a)[]参数。这在稍后的c++ 17中被认为是可以修复/允许的(http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393)

#2


6  

An incomplete type is allowed in contexts where the size doesn't need to be known.

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

With this declaration:

这个声明:

int a[][]

It is invalid even as a function parameter because the size of one array dimension is needed to know how to perform pointer arithmetic on the second dimension.

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

This however is valid:

这不过是有效的:

int (*a)[];

Because the size of the array doesn't need to be known in order to use a pointer to it.

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

Section 6.2.7 of the C standard gives an example of a declaration like this:

C标准的第6.2.7节给出了这样一个声明的示例:

5 EXAMPLE Given the following two file scope declarations:

给出以下两个文件范围声明的示例:

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

The resulting composite type for the function is:

该函数的合成类型为:

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

This example shows a declaration of type double (*)[3] that is compatible with a declaration of type double (*)[]

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

You can't however directly use this like a 2D array because of the missing size. Here are some examples to illustrate. If you attempt to do this:

但是由于缺少尺寸,您不能像使用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]);
        }
    }
}

The compiler (as expected) tells you this:

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

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

You can get around this by taking advantage of the fact that an array, even of indeterminate size, decays to a pointer in most contexts:

你可以利用一个数组,即使是不确定的大小,在大多数情况下也会衰减成指针:

#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;
}

This compiles clean with the following output:

此编译干净,输出如下:

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

Even outside of a function, the int (*)[] syntax can be used:

即使在函数之外,也可以使用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;
}

#3


3  

EDIT

编辑

Having read through all the relevant parts of the standard, C11 6.7.6.2 and 6.7.6.3, I believe this is a compiler bug/non-conformance. it apparently boils down to the text that the committee sneaked into the middle of a paragraph concerning array delimiters. 6.7.6.2/1 emphasis mine:

在阅读了C11 6.7.6.2和6.7.6.3标准的所有相关部分后,我认为这是一个编译器错误/不符合。显然,这可以归结为委员会在一段关于数组分隔符的段落中偷偷地加入了文本。6.7.6.2/1我特别强调:

In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.

除了可选类型限定符和关键字静态外,[and]还可以分隔表达式或*。如果它们分隔表达式(指定数组的大小),则表达式应该具有整数类型。如果表达式是一个常量表达式,那么它的值应该大于零。元素类型不应是不完整的或函数类型。可选类型限定符和关键字静态只能出现在具有数组类型的函数参数的声明中,然后只能出现在最外层的数组类型派生中。

Now this is of course very poorly written, basically it says

这篇文章写得很糟糕

"peripheral feature of little interest, peripheral feature of little interest, peripheral feature of little interest, OUT OF THE BLUE HERE COMES SOME ARRAY ELEMENT TYPE SPECIFICATION NOT RELATED TO THE REST OF THIS PARAGRAPH, peripheral feature of little interest, peripheral feature of little interest,...."

“外围功能的小兴趣,外围功能的小兴趣,外围功能的小兴趣,突然来了一些数组元素类型规范与剩下的这一段,外围功能的小兴趣,外围功能的小兴趣,....”

So it is easy to misunderstand, fooled me.

所以很容易误解,愚弄我。

Meaning that int a[][] is always incorrect no matter where it is declared, since an array cannot be an array of incomplete type.

意思是,无论在哪里声明,int a[][]总是不正确的,因为数组不能是不完全类型的数组。

However, my original answer below raises some valid concerns regarding whether array decay should be done before or after the compiler decides if the type is incomplete or not.

但是,下面我的原始答案提出了一些合理的问题,即在编译器决定类型是否不完整之前或之后是否应该进行数组衰减。


Given the specific case void foo(int n, int a[][]); only, this is a function declaration. It is not a definition.

给定特定情况下为void foo(int n, int a[][]);只是,这是一个函数声明。它不是一个定义。

C11 6.7.6.3/12

C11 6.7.6.3/12

If the function declarator is not part of a definition of that function, parameters may have incomplete type

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

So first of all, parameters are allowed to have incomplete type in the function declaration. The standard is clear. Which is why code like this compiles just fine:

首先,参数在函数声明中可以有不完整的类型。的标准是明确的。这就是为什么像这样的代码编译得很好:

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

Furthermore:

此外:

C11 6.7.6.3/4

C11 6.7.6.3/4

After adjustment, the parameters in a parameter type list in a function declarator that is part of a definition of that function shall not have incomplete type.

在调整后,函数声明器中的参数类型列表中的参数是该函数定义的一部分,不具有不完全类型。

After adjustment is very important here.

在这里,调整之后是非常重要的。

Meaning that after adjusting int a[][] to int (*a)[], the parameter shall not have incomplete type. It does not, it is a pointer to incomplete type, which is always allowed and perfectly fine.

表示将int a[][][]调整为int (*a)[]后,参数不应具有不完备类型。它不是,它是一个指向不完整类型的指针,它总是允许的,而且非常好。

The compiler is not allowed to first evaluate int a[][] as an incomplete array of incomplete arrays, and then later adjust it (if it found that the type was not incomplete). This would directly violate 6.7.6.3/4.

编译器不允许首先将int a[][]作为不完整数组的不完整数组求值,然后再对其进行调整(如果发现类型不是不完整的)。这直接违反了6。7。3/4。