`sizeof` 的操作数是用 VLA 计算的吗?

Is the operand of `sizeof` evaluated with a VLA?

评论区的一个论点促使我提出这个问题。

在下面的代码中,bar指向一个变长数组,所以sizeof是在运行时而不是编译时确定的。

int foo = 100;
double (*bar)[foo];

争论的焦点是当操作数是可变长度数组时,是否使用 sizeof 计算其操作数,从而在 bar 未初始化时导致 sizeof(*bar) 未定义行为。

使用 sizeof(*bar) 是否是未定义的行为,因为我取消引用了一个未初始化的指针? sizeof 的操作数是在类型为可变长度数组时实际计算的,还是仅确定其类型(sizeof 通常如何工作)?


编辑:每个人似乎都在引用 C11 草案中的 this passage。有谁知道这是不是官方标准中的写法?

是的,这会导致未定义的行为。

在 N1570 6.5.3.4/2 中我们有:

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.

现在我们有一个问题:*bar的类型是变长数组类型吗?

由于 bar 被声明为指向 VLA 的指针,取消引用它应该会产生一个 VLA。 (但我没有看到具体的文字说明它是否这样做)。

注意:可以在这里进行进一步的讨论,也许可以说 *bar 的类型 double[100] 不是 VLA.

假设我们同意 *bar 的类型实际上是一个 VLA 类型,那么在 sizeof *bar 中,表达式 *bar 被评估。

bar 在这一点上是不确定的。现在查看 6.3.2.1/1:

if an lvalue does not designate an object when it is evaluated, the behavior is undefined

由于 bar 不指向对象(由于不确定),评估 *bar 会导致未定义的行为。

事实上,标准似乎暗示行为是未定义的:

重新引用 N1570 6.5.3.4/2:

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.

我认为标准中的措辞令人困惑:操作数被求值并不意味着 *bar 将被求值。评估 *bar 不会以任何方式帮助计算其大小。 sizeof(*bar) 确实需要在 运行 时计算,但是为此生成的代码不需要取消引用 bar,它更有可能从保存结果的隐藏变量中检索大小信息bar 实例化时的大小计算。

另外两个答案已经引用了N15706.5.3.4p2:

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.

根据标准中的那一段,是的,sizeof 的操作数被评估。

我要争辩说这是标准中的一个缺陷; 某物 在 运行 时求值,但操作数不是。

让我们考虑一个更简单的例子:

int len = 100;
double vla[len];
printf("sizeof vla = %zu\n", sizeof vla);

根据标准,sizeof vla 计算表达式 vla。但这是什么意思?

在大多数情况下,评估数组表达式会产生初始元素的地址——但 sizeof 运算符是一个明确的例外。我们可能会假设评估 vla 意味着访问其元素的值,这具有未定义的行为,因为这些元素尚未初始化。但是没有其他上下文可以让数组表达式的计算访问其元素的值,在这种情况下绝对不需要这样做。 (更正:如果使用字符串文字来初始化数组对象,则计算元素的值。)

当执行 vla 的声明时,编译器将创建一些匿名元数据来保存数组的长度(它必须这样做,因为在 [= 之后将新值分配给 len 17=] 被定义和分配不会改变 vla) 的长度。确定 sizeof vla 所需要做的就是将该存储值乘以 sizeof (double)(或者如果它以字节为单位存储大小,则只检索存储值)。

sizeof 也可以应用于带括号的类型名称:

int len = 100;
printf("sizeof (double[len]) = %zu\n", sizeof (double[len]));

根据标准,sizeof 表达式计算 类型 。那是什么意思?显然它必须评估 len 的当前值。另一个例子:

size_t func(void);
printf("sizeof (double[func()]) = %zu\n", sizeof (double[func()]));

这里的类型名包含函数调用。计算 sizeof 表达式必须调用函数。

但在所有这些情况下,实际上没有必要计算数组对象的元素(如果有的话),这样做也没有意义。

sizeof 应用于 VLA 以外的任何东西都可以在编译时进行评估。 sizeof 应用于 VLA(对象或类型)时的区别在于 something 必须在 运行 时计算。但是要求值的不是sizeof的操作数;它只是确定操作数大小所需的任何东西,它永远不是操作数本身。

标准规定,如果 sizeof 的操作数是可变长度数组类型,则计算该操作数。这是标准的缺陷。

回到问题中的例子:

int foo = 100;
double (*bar)[foo] = NULL;
printf("sizeof *bar = %zu\n", sizeof *bar);

我在 NULL 中添加了一个初始化,以便更清楚地表明取消引用 bar 具有未定义的行为。

*bardouble[foo]类型,是VLA类型。原则上,*bar 被评估,这将有未定义的行为,因为 bar 未初始化。但同样,没有必要取消引用 bar。编译器在处理double[foo]类型时会生成一些代码,包括将foo(或foo * sizeof (double))的值保存在一个匿名变量中。评估 sizeof *bar 所要做的就是检索该匿名变量的值。如果更新标准以一致地定义 sizeof 的语义 ,那么很明显,评估 sizeof *bar 定义明确并产生 100 * sizeof (double) 无需取消引用bar.