具有副作用的变长数组参数大小表达式

问题描述 投票:0回答:3

这个问题源自 Eric Postpischil 在另一个帖子中的评论

我很难理解如何使用可变长度数组 (VLA) 作为函数参数:

  • 未检查数组大小。
  • 数组大小无法从数组中恢复,因为标准类型调整数组 -> 指针也适用于 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; }


c parameter-passing language-lawyer variable-length-array
3个回答
4
投票

进入函数时,将评估每个可变修改参数的大小表达式...

根据 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
不能为空,编译器可以用它来优化一些东西。但是,我没有任何示例来说明数字本身如何帮助优化或编译的其他方面。
    


4
投票
...可以省略大小变量和...将在运行时评估的大小,...;但有什么用呢?

顶级大小信息丢失了😢,因为数组参数现在是指针参数。然而,通过将 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



1
投票

只有指向数组的指针才有意义,因为这样可以更轻松地遍历数组并提供正确的大小信息

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
© www.soinside.com 2019 - 2024. All rights reserved.