这个问题源自 Eric Postpischil 在另一个帖子中的评论 。
我很难理解如何使用可变长度数组 (VLA) 作为函数参数:
sizeof()
调用所示;即使完全有可能在堆栈上传递整个数组,就像定义 VLA 时在堆栈上创建一样。那么,如果 VLA 参数没有提供任何优势并且像任何其他指针数组参数一样进行调整,那么为什么该语言允许使用 VLA 参数声明函数呢?如果语言不使用大小表达式(例如,检查实际参数的大小)并且在函数内部无法获得大小表达式(仍然必须为此传递一个显式变量),为什么要对大小表达式进行求值??
为了更清楚地说明我的困惑,请考虑以下程序(现场示例此处)。所有函数声明显然都是等效的。但正如 Eric 在另一个线程中指出的那样,函数声明中的参数大小表达式是在运行时求值的。尺寸表达式不会被忽略。
我不清楚这会带来什么好处,因为大小及其评估没有影响(除了可能的副作用)。特别是,重复一遍,该信息不能被函数内部的代码使用。最明显的变化是在类似堆栈的结构上传递 VLA。毕竟,它们通常也在调用方的堆栈上。但与恒定长度的数组一样,类型在声明时已被调整为指针 - 下面的所有声明都是等效的。尽管如此,还是评估了无用且被丢弃的数组大小表达式。
#include <stdio.h>
// Nothing to see here.
extern void ptr(int *arr);
// Identical to the above.
extern void ptr(int arr[]);
// Still identical. Is 1 evaluated? Who knows ;-).
extern void ptr(int arr[1]);
// Is printf evaluated when called? Yes.
// But the array is still adjusted to a pointer.
void ptr(int arr[printf("Call-time evaluation of size parameter\n")]){}
// This would not compile, so the declarations above must be equivalent.
// extern void ptr(int **p);
int main()
{
ptr(0);
ptr(0);
return 0;
}
进入函数时,将评估每个可变修改参数的大小表达式...
根据 6.7.6 3,可变修改类型是在其声明符中具有可变长度数组类型的类型,可能是嵌套的。 (因此
int a[n]
会被可变地修改,因为它是可变长度数组,固定长度
int (*a[3])[n]
也会被可变地修改,因为嵌套在其中是可变长度数组类型。)在
void foo(int n, int a[][n])
的情况下,我们看到必须计算
n
,因为编译器需要大小来计算 a[i][j]
等表达式的地址。然而,对于void foo(int n, int a[n])
来说,这个需求并不存在,我也不清楚上面引用的文字是否适用于调整前(int a[n]
)或调整后(int *a
)的参数类型。我记得,几年前,当我第一次注意到这一点时,我发现对于直接数组参数,两个编译器都会计算表达式,而另一个编译器不会计算表达式。调用用
foo
定义的
void foo(int a[printf("Hello, world.\n")]) {}
会或不会打印字符串,具体取决于编译器。目前,在 macOS 10.14.2 上使用 Apple LLVM 10.0.0 和 clang-1000.11.45.5 进行编译确实会打印字符串。 (如上所述,对于嵌套数组类型,必须对表达式求值,我尝试过的所有编译器都表现出了这一点。不幸的是,我目前不记得这些编译器是什么。)尚不清楚数组大小对编译器是否有用。 C 标准的这个方面可能还没有完全解决。有一个特征可以给尺寸增加一些意义:如果尺寸用
static
:
声明
void foo(int a[static SomeExpression]) { … }
然后,根据 6.7.6.3 7,
a
必须至少指向
SomeExpression
个元素。这意味着 a
不能为空,编译器可以用它来优化一些东西。但是,我没有任何示例来说明数字本身如何帮助优化或编译的其他方面。顶级大小信息丢失了😢,因为数组参数现在是指针参数。然而,通过将 2D VLA 函数参数转换为指向 1D 数组的指针,代码可以了解该数组的维度。
void g(size_t size, size_t size2, int arr[size][size2]) {
printf("g: %zu\n", sizeof(arr));
printf("g: %zu\n", sizeof(arr[0]));
}
int main(void) {
int arr[10][7];
g(10, 7, arr);
}
输出
g: 8 pointer size
g: 28 7 * int size
void g2(size_t size, size_t size2, int (*arr)[size][size2]) {
printf("g2: %zu\n", sizeof(arr));
printf("g2: %zu\n", sizeof(*arr));
}
int main(void) {
int arr[10][7];
g2(10, 7, &arr);
}
输出
g2: 8 pointer size
g2: 280 10 * 7 * int size
只有指向数组的指针才有意义,因为这样可以更轻松地遍历数组并提供正确的大小信息
int foo(int (*p)[2])
{
printf("sizeof int = %zu\n", sizeof(int));
printf("p + 0:%p\n", (void *)p);
printf("p + 1:%p\n", (void *)(p + 1));
}
void g(size_t size, size_t size2, int (*arr)[size][size2])
{
printf("g: %zu\n", sizeof(*arr));
printf("g: %zu\n", sizeof(*arr[0]));
}
int main()
{
foo(0);
g(5,5,0);
return 0;
}
sizeof int = 4
p + 0:(nil)
p + 1:0x8
g: 100
g: 20