正好在ISO / IEC9899中找到了什么我偶然发现了这个:
6.7.6输入姓名
[...]
语义
2 在几种情况下,有必要指定一种类型。这是使用类型完成的 name,在语法上是函数的声明或该类型的对象 省略标识符.128) 3示例构造
(a) int
(b) int *
(c) int *[3]
(d) int (*)[3]
(e) int (*)[*]
(f) int *()
(g) int (*)(void)
(h) int (*const [])(unsigned int, ...)
分别命名类型(a)int,(b)指向int的指针,(c)指向int的三个指针的数组,(d)指向一个指针的指针 三个整数的数组,(e)指向未指定数量的整数的可变长度数组的指针,(f)函数 没有参数规范返回指向int的指针,(g)指向没有参数的函数的指针 返回一个int,以及(h)一组未指定数量的常量指针到函数,每个指针都有一个 具有unsigned int类型的参数和未指定数量的其他参数,返回一个 中间体
让我最困惑的是:
(e)指向未指定数量的整数的可变长度数组
我或多或少能理解的其他人。但是指向未指定数量的'指针的指针的使用是什么?
甚至还需要编译器来支持
的语法int foo[*];
编辑以澄清
这个问题主要针对"是否有必要为编译器支持这个?"。
虽然这篇文章ANSI-C grammar - array declarations like [*] et alii明显改善了我的知识。仍然没有答案:为什么编译器需要知道原型的参数是否是包含未知大小的地址。如同简单地做int foo[]
或者它将是未指定的大小?
这是否真的需要得到支持? 如果不是这样,为什么标准甚至会实现这种语义?
答案 0 :(得分:9)
为什么编译器需要知道原型的参数是否是包含未知大小的地址。如同简单地做int foo []或者它将是未指定的大小?
编译器不需要“知道”任何东西,它是一种工具。
int (*)[*]
和int[]
之间的差异与int (*)[5]
和int[]
之间的差异大致相同。如果你同意后一对不可互换,那么前者也不是。
在C99之前,指定未知数量T
元素的数组的方法是T[]
。这是一个不完整的类型,这意味着您不能拥有T[]
数组。没有T[][]
。在函数声明符中,T[]
表示与T*
相同。 OTOH T[*]
是一个可变长度数组,它与未知数量的元素数组不同。您可以拥有一组可变大小的数组,即是 T[*][*]
。您要问的语法是支持此可变大小数组类型所必需的。幸运的是,你不是在问为什么我们需要不同的类型,因为答案真的很啰嗦,但这是我的抨击。
类型的目的是双重的。首先,对象代码生成需要类型(a++
之类的东西通常生成不同的对象代码,具体取决于a
的类型)。其次,类型检查需要类型(a++
可能允许或不允许,具体取决于a
的类型。)
[*]
类型仅允许在不属于函数定义的函数声明符中。所以代码生成和这里没有关系。这让我们进行了类型检查。事实上,
int foo(int, int (*)[*]);
int bar(int, int (*)[5]);
int main ()
{
int a;
int aa[5];
int aaa[5][5];
foo(1, &a); // incorrect, `&a` is `int*`, `int*` and `int (*)[*]` are different
bar(1, &a); // incorrect, `&a` is `int*`, `int*` and `int (*)[5]` are different
foo(5, aa); // incorrect, `aa` is `int*` (!), `int*` and `int (*)[*]` are different
bar(5, aa); // incorrect, `aa` is `int*` (!), `int*` and `int (*)[5]` are different
foo(5, &aa); // correct
bar(5, &aa); // correct
foo(5, aaa); // correct
bar(5, aaa); // correct
}
如果我们同意哪些来电bar
是正确的,哪些不正确,我们也必须同意拨打foo
。
唯一剩下的问题是,为什么int foo(int m, int (*)[m]);
不足以达到此目的?它可能是,但C语言不强制程序员在不需要参数名称的函数声明符中命名形式参数。 [*]
在VLA的情况下允许这种小的自由。
答案 1 :(得分:6)
我将严格按照要求回答您的问题:
本课题主要针对“是否有必要为编译器提供支持?”
对于C99编译器,是:它是标准的一部分,因此符合C99的编译器必须支持它。 int foo[*];
有用的问题与是否必须得到支持的问题完全正交。声称符合我测试的C99的所有编译器都支持它(但我不确定它对它有用)。
对于C11编译器,好消息!可变长度阵列已成为“条件特征”。只要定义__STDC_NO_VLA__
:
6.10.8.3条件特征宏
...
__STDC_NO_VLA__
整数常量1,用于表示实现不支持可变长度数组或可变修改类型。
答案 2 :(得分:1)
如果我将一个具有多个维度的数组传递给函数,并且如果用于表示数组给定维度中元素数量的函数参数位于数组参数本身之后,那么[*]
语法可以使用。对于具有两个以上维度的数组,并且如果数组参数再次位于元素计数参数之前,则必须使用此语法 ,因为数组衰减仅发生一次。毕竟,你不能很好地使用int (*)[][]
或int [][][]
因为标准要求在int [A][B]
和int [A][B][C][D]
中,由于数组衰减到只有A可能会被省略一个指针。如果在函数参数中使用指针表示法,则允许使用int (*)[]
,但这对我来说没什么意义,特别是因为:
sizeof ptr[0]
和sizeof *ptr
都是非法的 - 编译器应该如何确定具有不确定元素数的数组的大小?相反,您必须在运行时使用N * sizeof **ptr
或sizeof(int (*)[N])
找到它。这也意味着ptr
上的任何算术运算,例如++ptr
的使用,都是非法的,因为它们依赖于无法计算的大小信息。可以使用类型转换来解决此问题,但是使用具有正确类型信息的局部变量更容易。那么,为什么不直接使用[*]
语法并从头开始包含正确的类型信息?sizeof ptr[0][0]
是非法的,但sizeof (*ptr)[0]
不是 - 即使只是获取大小信息,仍会执行数组索引,因此就像编写sizeof (*(ptr + 0))[0]
一样,这是非法的,因为你不能像前面提到的那样将算术运算应用于不完整的类型。[]
可以被*
取代,而int **
代替int (*)[]
,这是不正确的,因为该子数组没有腐烂。 数组衰减仅发生一次。 我注意到如果首先使用作为元素计数的参数,那么[*]
语法是不必要的,这是真的,但是最后一次有人看到以下任何内容时是什么时候?
void foo (int a, int b, int c, int arr[a][b][c]);
void bar (int a, int b, int c, int arr[][b][c]);
void baz (int a, int b, int c, int (*arr)[b][c]);
所以回答你的问题:
和
可能需要[*]
语法。我实际上鼓励使用[*]
,因为[]
在需要大小信息时会出现问题。
答案 3 :(得分:0)
In C99 it is possible to declare arrays using variable dimensions, providing the variable has a ( positive integer ) value at the time the declaration is made. It turns out that this carries over to the declaration of arrays in function parameters as well, which can be particularly useful for multi-dimensional arrays.
For example, in the prototype:
int arrayFunction( int nRows, int nCols, double x[ nRows ],
double y[ nRows ][ nCols ] );
the variable dimension on x is informative to the human but not necessary for the computer, since we could have declared it as x[ ]. However the nCols dimension on y is very useful, because otherwise the function would have to be written for arrays with pre-determined row sizes, and now we can write a function that will work for arrays with any row length.
要使两个数组类型兼容,两者必须具有兼容的元素类型,并且如果两个大小说明符都存在且是整数常量表达式,则两个大小必须具有相同的值。如果VLA都具有相同的元素类型,则VLA始终与另一个数组类型兼容。如果在要求它们兼容的上下文中使用这两种数组类型,那么如果维度大小在运行时不相等,则它是未定义的行为
答案 4 :(得分:-1)
如果您希望使用“锯齿状”数组,当该矩阵的行的大小(在编译时未知)将在运行时初始化并且在整个过程中保持不变时,它可能很有用执行时间处理时间。但是为了确保你将保持每一行的界限,你必须以某种方式存储该数组的实际大小,如果希望它“锯齿”,则分别存储每行,因为sizeof运算符不能正常运行时初始化数组(它最多会返回指针的大小,因为它是一个编译时运算符。)