`sizeof`的操作数是用VLA评估的吗?

这个答案的评论部分中的一个论点促使我提出这个问题。

在以下代码中, bar指向可变长度数组,因此sizeof是在运行时而不是编译时确定的。

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

参数是关于当操作数是可变长度数组时是否使用sizeof评估其操作数,使得当未初始化bar时, sizeof(*bar)未定义行为。

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


编辑:每个人似乎都在引用C11选秀中的这段话 。 有谁知道这是否是官方标准中的措辞?

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

在N1570 6.5.3.4/2中我们有:

sizeof运算符产生其操作数的大小(以字节为单位),该操作数可以是表达式或类型的带括号的名称。 大小由操作数的类型确定。 结果是整数。 如果操作数的类型是可变长度数组类型,则计算操作数 ; 否则,不评估操作数,结果是整数常量。

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

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

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

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

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

如果左值在评估时未指定对象,则行为未定义

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

其他两个答案已经引用了N1570 6.5.3.4p2:

sizeof运算符产生其操作数的大小(以字节为单位),该操作数可以是表达式或类型的带括号的名称。 大小由操作数的类型确定。 结果是整数。 如果操作数的类型是可变长度数组类型,则计算操作数; 否则,不评估操作数,结果是整数常量。

根据标准中的那一段,是的,对sizeof的操作数进行了评估。

我要说这是标准中的缺陷; 在运行时评估某些东西 ,但操作数不是。

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

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

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

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

当执行vla的声明时,编译器将创建一些匿名元数据来保存数组的长度(它必须,因为在定义vla并且分配不改变vla的长度之后为len分配新值)。 确定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表达式必须调用该函数。

但是在所有这些情况下,没有实际需要评估数组对象的元素(如果有的话),并且没有必要这样做。

应用于VLA以外的任何东西的sizeof可以在编译时进行评估。 将sizeof应用于VLA(对象或类型)时的差异是必须在运行时评估某些内容 。 但是必须要评估的东西不是sizeof的操作数; 它只是确定操作数大小所需要的,而不是操作数本身。

该标准表示如果该操作数是可变长度数组类型,则评估sizeof操作数。 这是标准中的缺陷。

回到问题中的示例:

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

我已经向NULL添加了一个初始化,以使得解除引用bar具有未定义的行为更加清晰。

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

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

重新引用N1570 6.5.3.4/2:

sizeof运算符产生其操作数的大小(以字节为单位),该操作数可以是表达式或类型的带括号的名称。 大小由操作数的类型确定。 结果是整数。 如果操作数的类型是可变长度数组类型,则计算操作数; 否则,不评估操作数,结果是整数常量。

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