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

Pet*_*ica 7 c parameter-passing language-lawyer variable-length-array

这个问题源于 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;
}
Run Code Online (Sandbox Code Playgroud)

Eri*_*hil 6

C 2018 6.9.1 讨论了函数定义并在第 10 段中告诉我们:

\n\n
\n

在进入该函数时,将评估每个可变修改参数的大小表达式\xe2\x80\xa6

\n
\n\n

根据 6.7.6 3,可变修改类型是在其声明符中具有可变长度数组类型的类型,可能是嵌套的。(因此int a[n]可以进行可变修改,因为它是一个可变长度数组,而固定长度数组int (*a[3])[n]由于嵌套在其中是可变长度数组类型,因此固定长度也可以进行可变修改。)

\n\n

在 的情况下void foo(int n, int a[][n]),我们看到n必须计算 ,因为编译器需要大小来计算诸如 之类的表达式的地址a[i][j]。然而,对于 来说void foo(int n, int a[n]),这种需要并不存在,并且我不清楚上面引用的文字是否适用于调整前的参数类型(int a[n])还是调整后的参数类型(int *a)的参数类型。

\n\n

我记得,几年前,当我第一次注意到这一点时,我发现一个编译器对直接数组参数的表达式进行求值,而另一个编译器则不求值。调用foo已定义的void foo(int a[printf("Hello, world.\\n")]) {}会或不会打印字符串,具体取决于编译器。目前,在 macOS 10.14.2 上使用 Apple LLVM 10.0.0 和 clang-1000.11.45.5 进行编译确实会打印字符串。(如上所述,对于嵌套数组类型,必须对表达式求值,我尝试过的所有编译器都表现出了这一点。不幸的是,我目前不记得这些编译器是什么。)

\n\n

目前尚不清楚数组大小对编译器是否有用。C 标准的这个方面可能还没有完全解决。有一个特征可以给尺寸增加一些意义:如果尺寸声明为static

\n\n
void foo(int a[static SomeExpression]) { \xe2\x80\xa6 }\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么,根据 6.7.6.3 7,a必须至少指向SomeExpression元素。这意味着a不能为空,编译器可以使用它来优化一些东西。但是,我没有任何示例来说明数字本身如何帮助优化或编译的其他方面。

\n

  • @PeterA.Schneider:`int (puts) (); int main(p, q) int p; char *q [(puts) (&amp;*“你好,世界。”)]; {}` 是一个完整的 C 程序,即使使用 `-std=c11 -pedantic -Wall`,在 Clang 中编译也不会出现警告。切勿使用它。:-) (2认同)

chu*_*ica 5

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

顶层大小信息丢失,因为数组参数现在是指针参数。然而,通过将 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);
}
Run Code Online (Sandbox Code Playgroud)

输出

g: 8   pointer size
g: 28  7 * int size
Run Code Online (Sandbox Code Playgroud)

或者,传递一个指向数组的指针。

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);
}
Run Code Online (Sandbox Code Playgroud)

输出

g2: 8    pointer size
g2: 280  10 * 7 * int size
Run Code Online (Sandbox Code Playgroud)