什么是'应仅访问先前值以确定要存储的值'意思是

What does 'prior value shall be accessed only to determine the value to be stored' mean?

本文关键字:意思是 存储 访问 什么      更新时间:2023-10-16

Prasoon对"未定义的行为和序列点"问题的回答,我不明白以下的含义

应仅访问先前的值以确定要存储的值。

举个例子,下面的例子被引用为在C++中具有未定义的行为

  1. a[i] = i++;
  2. int x = i + i++;

尽管上面给出了解释,但我不理解这一部分(我认为我正确理解了答案的其余部分)。


我不明白上面的代码示例有什么问题。我认为这些编译器有定义良好的步骤,如下所示。

a[i] = i++;

  • a[i] = i;
  • i = i + 1;

int x = i + i++ ;

  • x = i + i;
  • i = i + 1;

我错过了什么?"只能访问以确定要存储的值"是什么意思?

另请参阅这个问题和我的答案。我不会投票将其作为重复项结束,因为你问的是C++而不是C,但我相信这两种语言的问题是相同的。

应仅访问先前的值以确定要存储的值。

这似乎是一个奇怪的要求;为什么标准应该关心为什么访问值?当您意识到,如果读取前一个值以确定要存储在同一对象中的值,这将隐含地对两个操作施加排序,因此读取必须在写入之前进行,这是有意义的。由于这种顺序,对同一对象的两次访问(一次读取,一次写入)是安全的。编译器不能以导致代码相互干扰的方式重新排列(优化)代码。

另一方面,在这样的表达式中

a[i] = i++

i有三种访问:左侧的读取以确定要修改a的哪个元素,右侧的读取以决定要增加的值,以及将增加的值存储回i的写入。RHS上的读和写是可以的(i++本身是安全的),但在LHS上的读取和RHS上写之间没有定义的顺序。因此,编译器可以自由地以改变读写操作之间关系的方式重新排列代码,而标准则象征性地举起手来,不定义行为,对可能的后果只字不提。

C11和C++11都更改了这一领域的措辞,明确了一些订购要求。"先验价值"的措辞已不复存在。引用C++11标准草案,1.9p15:

除非另有说明,否则单独运算符的操作数求值并且对各个表达式的子表达式的进行不排序。[…]运算符的操作数的值计算是按顺序进行的在运算符的结果的值计算之前。如果一方标量对象上的效果相对于对同一标量对象或值计算的另一个影响使用相同标量对象的值,行为是未定义的。

a[i] = i++;

对CCD_ 13进行了修改。还读取i以确定要使用a的哪个索引,这不影响对i的存储。这是不允许的。

int x = i + i++;

对CCD_ 17进行了修改。i还用于计算要存储到x中的值,该值不影响对i的存储。这是不允许的。

由于标准规定"只能访问先前的值来确定要存储的值",因此编译器不需要遵循您概述的"定义良好"的步骤。

他们通常不会。

对于您的特定示例,标准的措辞意味着允许编译器对步骤进行如下排序:

a[i] = i++;
  • i = i + 1;
  • a[i] = i;

int x = i + i++ ;

  • i = i + 1;
  • x = i + i;

这给出了一个与你想象中的明确顺序完全不同的结果。编译器也可以做任何其他它可能喜欢的事情,即使它对你来说比我刚才键入的内容更有意义。这就是未定义行为的含义。

虽然像x=y+z;这样的语句在语义上等同于temp=y; temp+=z; x=temp;,但编译器通常不需要(除非xvolatile)以这种方式实现它。在一些平台上,它可以作为CCD_ 30更有效地执行。除非变量是volatile,否则编译器为赋值生成的代码可以向其写入任何值序列,前提是:

  1. 任何有权读取变量"旧"值的代码都会根据赋值前的值进行操作。

  2. 任何有权读取变量"新"值的代码都会根据给定的最终值进行操作。

给定i=511; foo[i] = i++;,编译器将有权将值5写入foo[511]foo[512],但同样有权将其存储到foo[256]foo[767]foo[24601]或任何其他位置。由于编译器有权将值存储在foo的任何可能位移处,并且编译器有权对向指针添加过大位移的代码执行任何它喜欢的操作,因此这些权限加在一起实际上意味着编译器可以对foo[i]=i++;执行任何它想做的操作。

请注意,在理论上,如果i是16位unsigned int,但foo是65536元素或更大的数组(在经典Macintosh上完全可能),则上述权限将允许给定foo[i]=i++;的编译器写入foo的任意值,但不执行任何其他操作。在实践中,《标准》避免了这种细微的区别。当给定foo[i]=i++;这样的表达式时,说标准对编译器的操作没有任何要求要比说编译器的行为在某些狭窄的情况下受到约束而在其他情况下没有受到约束容易得多。