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

问题描述 投票:17回答:3

this answer评论部分的一个论点促使我提出这个问题。

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

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

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

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


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

c sizeof variable-length-array
3个回答
10
投票

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

在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会导致未定义的行为。


11
投票

另外两个答案已经引用了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的声明时,编译器将创建一些匿名元数据来保存数组的长度(它必须,因为在定义len并且分配后不会改变vla的长度时为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(对象或类型)时的差异在于必须在运行时评估某些内容。但是必须要评估的事情不是sizeof的操作数;它只是确定操作数大小所需要的,而不是操作数本身。

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

回到问题中的示例:

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

我已经为NULL添加了一个初始化,以便更清楚地解释引用bar有未定义的行为。

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


2
投票

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

重新引用N1570 6.5.3.4/2:

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

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

© www.soinside.com 2019 - 2024. All rights reserved.