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

时间:2019-01-26 16:27:37

标签: c parameter-passing language-lawyer variable-length-array

这个问题源于Eric Postpischil发in another thread.

的评论。

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

  • 未检查数组大小。
  • 无法从数组中恢复数组大小,因为标准类型调整数组->指针也适用于VLA,如下面的sizeof()调用所示;即使完全有可能在堆栈上传递整个数组,就像在定义VLA时在堆栈上创建一样。
  • 必须将大小作为附加参数传递,就像使用指针一样。

那么,如果该语言没有提供任何优势并且像指针的任何其他数组参数一样进行调整,那么为什么为什么允许使用VLA参数声明函数呢?如果语言不使用大小表达式(例如,检查实际参数的大小)并且在函数内部无法获得大小表达式(仍然必须为此传递一个显式变量),为什么要对大小表达式进行评估??

为了使我更加困惑,请考虑以下程序(实时示例here)。所有函数声明显然都是等效的。但是正如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;
}

3 个答案:

答案 0 :(得分:2)

  

...可以省略size变量和...将在运行时求值的大小...;但是有什么用呢?

顶级大小信息丢失了,在array参数中现在是一个指针参数。然而,有了2D VLA函数参数,这些参数变成了指向一维数组的指针,代码就知道了该数组的维数。

textFieldDidEndEditing

输出

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);
}

答案 1 :(得分:2)

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

  

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

根据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])或调整后({{1 }}。

我记得,几年前第一次引起我注意时,我发现对于直接数组参数,它确实评估了表达式,而没有评估表达式。调用由int *a定义的foo会或不会打印字符串,具体取决于编译器。当前,在macOS 10.14.2上使用Apple LLVM 10.0.0和clang-1000.11.45.5进行编译不会打印字符串。 (如上所述,对于嵌套数组类型,必须对表达式进行求值,而我尝试过的所有编译器都具有该表达式。不幸的是,我目前不记得这些编译器是哪个。)

目前尚不清楚数组大小对编译器是否有用。 C标准的这一方面可能尚未完全解决。有一个功能为尺寸增加了一些含义。如果尺寸是用void foo(int a[printf("Hello, world.\n")]) {}声明的:

static

然后,按照6.7.6.3 7,void foo(int a[static SomeExpression]) { … } 必须指向至少a个元素。这意味着SomeExpression不能为null,编译器可以使用它来优化某些内容。但是,我没有任何示例可以说明数字本身如何有助于优化或其他方面的编译。

答案 2 :(得分:1)

我看不到VLA作为函数参数有任何实际用途。

只有指向数组的指针才有意义,因为它们使遍历数组更容易并给出正确的大小信息

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