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

时间:2015-10-07 06:45:54

标签: c sizeof variable-length-array

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

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

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

论证是关于在操作数是可变长度数组时是否使用sizeof计算其操作数,在sizeof(*bar)未初始化时使bar未定义行为。

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

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

3 个答案:

答案 0 :(得分: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表达式必须调用函数。

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

可以在编译时评估应用于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

答案 1 :(得分: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会导致未定义的行为。

答案 2 :(得分:2)

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

重新引用N1570 6.5.3.4/2:

  

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

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