this answer的评论部分中的一个论点促使我提出这个问题。
在以下代码中,bar
指向可变长度数组,因此sizeof
在运行时而不是编译时确定。
int foo = 100;
double (*bar)[foo];
论证是关于在操作数是可变长度数组时是否使用sizeof
计算其操作数,在sizeof(*bar)
未初始化时使bar
未定义行为。
使用sizeof(*bar)
是不确定的行为,因为我取消引用未初始化的指针?当类型是可变长度数组时,sizeof
的操作数是否实际被评估,或者只是确定其类型(sizeof
通常如何工作)?
编辑:每个人似乎都在引用C11草案中的this passage。有谁知道这是否是官方标准中的措辞?
答案 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
实例化时。