a[][] 和 (*a)[] 不等同于函数参数吗?
Aren't a[][] and (*a)[] equivalent as function parameters?
函数原型
void foo(int n, int a[][]);
给出关于不完整类型的错误,而
void foo(int n, int (*a)[]);
编译。根据衰减规则 int a[][]
在这种情况下等同于 int (*a)[]
因此 int (*a)[]
也应该给出关于不完整类型的错误但 GCC 似乎接受它。我有什么想念的吗?
这可能是一个 GCC 错误,但我没有找到任何与之相关的内容。
在不需要知道大小的上下文中允许使用不完整的类型。
有了这个声明:
int a[][]
即使作为函数参数也是无效的,因为需要一个数组维度的大小才能知道如何在第二个维度上执行指针运算。
然而这是有效的:
int (*a)[];
因为不需要知道数组的大小就可以使用指向它的指针。
C standard 的第 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]);
此示例显示类型 double (*)[3]
的声明与类型 double (*)[]
的声明兼容
但是,由于缺少大小,您不能像二维数组一样直接使用它。下面举几个例子来说明。如果您尝试这样做:
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;
}
不,它们不等同于函数参数。它们与 foo
和 bar
中的参数声明不完全相同
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++ "fixed" 这个问题,允许在参数声明中使用不完整类型的数组。但是,原来的C++仍然禁止int a[][]
参数,int (&a)[]
参数甚至int (*a)[]
参数。这据说是 fixed/allowed 后来的 C++17 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393)
编辑
通读了标准的所有相关部分,C11 6.7.6.2 和 6.7.6.3,我相信这是一个编译器 bug/non-conformance。 它显然沸腾了一直到委员会潜入一段关于数组定界符的段落中间的文本。 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.
现在这个当然写的很烂了,基本上就是
"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,...."
所以很容易误会,骗我。
意思是int a[][]
无论在哪里声明总是不正确的,因为数组不能是不完整类型的数组。
但是,我下面的原始回答提出了一些关于数组衰减是否应该在编译器决定类型是否不完整之前或之后完成的有效担忧。
仅给定特定情况void foo(int n, int a[][]);
,这是一个函数声明。这不是一个定义。
C11 6.7.6.3/12
If the function declarator is not part of a definition of that
function, parameters may have incomplete type
所以首先,参数是允许在函数声明中有不完整的类型。标准很明确。这就是为什么像这样的代码编译得很好:
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
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.
调整后这里很重要。
意思是将int a[][]
调整为int (*a)[]
后,参数不能有不完整的类型。它不是,它是一个指向不完整类型的指针,它总是被允许并且完全没问题。
不允许编译器先将int a[][]
判断为不完整数组的不完整数组,然后再进行调整(如果发现类型不完整)。这将直接违反 6.7.6.3/4。
函数原型
void foo(int n, int a[][]);
给出关于不完整类型的错误,而
void foo(int n, int (*a)[]);
编译。根据衰减规则 int a[][]
在这种情况下等同于 int (*a)[]
因此 int (*a)[]
也应该给出关于不完整类型的错误但 GCC 似乎接受它。我有什么想念的吗?
这可能是一个 GCC 错误,但我没有找到任何与之相关的内容。
在不需要知道大小的上下文中允许使用不完整的类型。
有了这个声明:
int a[][]
即使作为函数参数也是无效的,因为需要一个数组维度的大小才能知道如何在第二个维度上执行指针运算。
然而这是有效的:
int (*a)[];
因为不需要知道数组的大小就可以使用指向它的指针。
C standard 的第 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]);
此示例显示类型 double (*)[3]
的声明与类型 double (*)[]
但是,由于缺少大小,您不能像二维数组一样直接使用它。下面举几个例子来说明。如果您尝试这样做:
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;
}
不,它们不等同于函数参数。它们与 foo
和 bar
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++ "fixed" 这个问题,允许在参数声明中使用不完整类型的数组。但是,原来的C++仍然禁止int a[][]
参数,int (&a)[]
参数甚至int (*a)[]
参数。这据说是 fixed/allowed 后来的 C++17 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393)
编辑
通读了标准的所有相关部分,C11 6.7.6.2 和 6.7.6.3,我相信这是一个编译器 bug/non-conformance。 它显然沸腾了一直到委员会潜入一段关于数组定界符的段落中间的文本。 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.
现在这个当然写的很烂了,基本上就是
"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,...."
所以很容易误会,骗我。
意思是int a[][]
无论在哪里声明总是不正确的,因为数组不能是不完整类型的数组。
但是,我下面的原始回答提出了一些关于数组衰减是否应该在编译器决定类型是否不完整之前或之后完成的有效担忧。
仅给定特定情况void foo(int n, int a[][]);
,这是一个函数声明。这不是一个定义。
C11 6.7.6.3/12
If the function declarator is not part of a definition of that function, parameters may have incomplete type
所以首先,参数是允许在函数声明中有不完整的类型。标准很明确。这就是为什么像这样的代码编译得很好:
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
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.
调整后这里很重要。
意思是将int a[][]
调整为int (*a)[]
后,参数不能有不完整的类型。它不是,它是一个指向不完整类型的指针,它总是被允许并且完全没问题。
不允许编译器先将int a[][]
判断为不完整数组的不完整数组,然后再进行调整(如果发现类型不完整)。这将直接违反 6.7.6.3/4。