何时以及如何在 sizeof 表达式中评估 VLA?

When and how are VLAs evaluated in sizeof expressions?

C 标准有这种语言:

6.5.3.4 The sizeof and _Alignof operators

Semantics

  1. The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.

我不清楚标准的含义如果操作数的类型是可变长度数组类型,则计算操作数

是否应该修改 C 标准的语言以进行澄清?

这是一个测试程序,用于说明 VLA 在某些特定情况下的行为:

#include <stdio.h>

static int N = 0;
int foo(void) { return ++N; }

int main() {
    typedef char S[foo()];      // foo() is called
    printf("typedef char S[foo()];\t");                             printf("N=%d\n", N);
    printf("sizeof(S)=%d\t\t", (int)sizeof(S));                     printf("N=%d\n", N);

    typedef char U[foo()];      // foo() is called
    printf("typedef char U[foo()];\t");                             printf("N=%d\n", N);
    printf("sizeof(U)=%d\t\t", (int)sizeof(U));                     printf("N=%d\n", N);

    S s1;
    printf("S s1;\t\t\t");                                          printf("N=%d\n", N);
    printf("sizeof(s1)=%d\t\t", (int)sizeof(s1));                   printf("N=%d\n", N);

    S s2;
    printf("S s2;\t\t\t");                                          printf("N=%d\n", N);
    printf("sizeof(s2)=%d\t\t", (int)sizeof(s2));                   printf("N=%d\n", N);

    U u1;
    printf("U u1;\t\t\t");                                          printf("N=%d\n", N);
    printf("sizeof(u1)=%d\t\t", (int)sizeof(u1));                   printf("N=%d\n", N);

    U *pu1 = &u1;
    printf("U *pu1 = &u1;\t\t");                                    printf("N=%d\n", N);
    printf("sizeof(*pu1)=%d\t\t", (int)sizeof(*pu1));               printf("N=%d\n", N);

    U *pu2 = NULL;
    printf("U *pu2 = NULL;\t\t");                                   printf("N=%d\n", N);
    // sizeof(*pu2) does not evaluate *pu2, contrary to the Standard specification
    printf("sizeof(*pu2)=%d\t\t", (int)sizeof(*pu2));               printf("N=%d\n", N);

    char x2[foo()][foo()];      // foo() is called twice
    printf("char x2[foo()][foo()];\t");                             printf("N=%d\n", N);
    printf("sizeof(x2)=%d\t\t", (int)sizeof(x2));                   printf("N=%d\n", N);
    printf("sizeof(x2[0])=%d\t\t", (int)sizeof(x2[0]));             printf("N=%d\n", N);

    // sizeof(char[foo()]) evaluates foo()
    printf("sizeof(char[foo()])=%d\t", (int)sizeof(char[foo()]));   printf("N=%d\n", N);
    return 0;
}

输出(clang 和 gcc):

typedef char S[foo()];  N=1
sizeof(S)=1             N=1
typedef char U[foo()];  N=2
sizeof(U)=2             N=2
S s1;                   N=2
sizeof(s1)=1            N=2
S s2;                   N=2
sizeof(s2)=1            N=2
U u1;                   N=2
sizeof(u1)=2            N=2
U *pu1 = &u1;           N=2
sizeof(*pu1)=2          N=2
U *pu2 = NULL;          N=2
sizeof(*pu2)=2          N=2
char x2[foo()][foo()];  N=4
sizeof(x2)=12           N=4
sizeof(x2[0])=4         N=4
sizeof(char[foo()])=5   N=5

每个可变修改类型都有一个大小,对于每个维度,该大小要么是该维度的倍数,要么独立于它。没有理由评估可变修改对象的大小应该要求评估不会影响对象大小的任何维度的值,但一些编译器可能会评估此类维度的值,因为可变修改类型的原始规则暗示他们应该被评估。在不同的实现以不同方式处理构造的情况下,标准的作者倾向于避免让标准建议任何一种行为都更好。因此,该标准故意对涉及可变修改类型的极端情况含糊不清,以避免必须将任何现有实现的行为描述为“错误”或劣质。

If the type of the operand is a variable length array type, it does not seem to serve any purpose to evaluate the argument as the size can be determined from the definition of the type, as it is stipulated in 6.7.6.2 Array declarators that The size of each instance of a variable length array type does not change during its lifetime.

但是直到数组在运行时实例化,该大小才为人所知。某种 的评估必须 在运行时执行。未指定评估的具体内容。

Should the language of the C Standard be amended for clarification?

我想是的,是的。我认为以下习惯用法对于动态分配行数和列数直到运行时才知道的二维数组非常有用:

int rows, cols;
...
T (*arr)[cols] = malloc( sizeof *arr * rows );

但是,按照标准当前的措辞,这(很可能)会调用未定义的行为,因为我在运行时评估 *arr,但 arr 在那一点。您不需要 取消引用 arr 来获取数组类型的大小,但不幸的是,标准中的语言并不是那么精细。我希望看到类似于“如果操作数的类型是可变长度数组类型,则计算操作数 以单独获取数组大小的目的 ” 的语言。