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

PC *_*ite 17 c sizeof variable-length-array

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

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

int foo = 100;
double (*bar)[foo];
Run Code Online (Sandbox Code Playgroud)

该论点是关于sizeof当操作数是可变长度数组时是否使用计算其操作数,在未初始化sizeof(*bar)时产生未定义的行为bar.

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


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

Kei*_*son 11

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

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

根据标准中的该段,是的,sizeof评估操作数.

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

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

int len = 100;
double vla[len];
printf("sizeof vla = %zu\n", sizeof vla);
Run Code Online (Sandbox Code Playgroud)

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

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

当声明vla被执行时,编译器将创建一些匿名元数据来保存数组的长度(它必须,因为为lenafter vla定义一个新值并且分配后不会改变长度vla).所有必须要确定的sizeof vla是将该存储值乘以sizeof (double)(或者只是为了检索存储的值,如果它以字节为单位存储大小).

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

int len = 100;
printf("sizeof (double[len]) = %zu\n", sizeof (double[len]));
Run Code Online (Sandbox Code Playgroud)

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

size_t func(void);
printf("sizeof (double[func()]) = %zu\n", sizeof (double[func()]));
Run Code Online (Sandbox Code Playgroud)

这里的类型名称包括函数调用.评估sizeof表达式必须调用该函数.

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

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

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

回到问题中的示例:

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

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

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

  • @PCLuddite:我的观点是这是标准中的缺陷。从字面上看,它强加了一个不必要的要求,没有意义,在某些情况下可能是不可能的(没有“评估”类型名称的含义的定义),而且我有理由确信确实如此不反映作者的意图。请参阅[此处](http://www.open-std.org/jtc1/sc22/wg14/www/docs/summary.htm),了解针对 C11 标准的缺陷报告列表。无论如何,我会稍微重新表述一下我的答案,说这是一个缺陷,而不是说它“不正确”。 (2认同)
  • @Olaf:不,我的意思是改变“len”的值*不会*改变“vla”的长度。我的意思是给定 `int len = 100; int vla[长度]; len = 200;`,将`len`的值更改为200不会影响`vla`的长度。我已经更新了我的答案以澄清这一点。 (2认同)
  • @Olaf:我在 2012 年在 comp.std.c 新闻组上开始讨论这一点;Google 网上论坛存档在 [此处](https://groups.google.com/forum/#!topic/comp.std.c/hvdN9kWexkw/discussion)。它并没有真正去任何地方——无论如何,C 标准委员会与 comp.std.c 没有任何官方联系。 (2认同)
  • @PCLuddite:老实说:如果标准与常识相矛盾,而我使用的编译器实际上 ** 确实遵循了,被广泛使用,得到了很好的支持并且不会将我锁定到特定的供应商,我不会等到委员会得到东西他们有超过 16 年(或 12 年,计算上两个版本之间的时间)时间(这是我实际想法的礼貌版本)。潘塔雷伊。 (2认同)

M.M*_*M.M 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会导致未定义的行为.

  • 在`int*pointer`之后,`pointer`的类型是`int*`,它不是VLA类型.`*vla`的类型为`int`,它不是VLA类型,因此也不进行评估. (2认同)
  • @MM:我很清楚!确切地说,问题是:为什么必须以不同的方式对待这两种情况?VLA 表达式 **必须** 进行求值,但生成 VLA 的(嵌套)表达式不需要求值:“*f”不需要求值,而只需解析以获取结果类型(对于“int *”),这是一个 VLA,然后必须对其进行评估(即索引)。如果不评估“*f”,则不存在 UB。 (2认同)