Ale*_*tov 44 c++ standards variable-assignment
请考虑以下C++标准ISO/IEC 14882:2003(E)引用(第5节,第4段):
除非另有说明,否则单个运算符的操作数和单个表达式的子表达式的评估顺序以及副作用发生的顺序是未指定的.53)在前一个和下一个序列点之间,标量对象应通过表达式的计算最多修改其存储值一次.此外,只能访问先前值以确定要存储的值.对于完整表达式的子表达式的每个允许排序,应满足本段的要求; 否则行为未定义.[例:
Run Code Online (Sandbox Code Playgroud)i = v[i++]; // the behavior is unspecified i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is unspecified i = i + 1; // the value of i is incremented
- 末端的例子]
我很惊讶,i = ++i + 1
给出了一个未定义的值i
.有没有人知道编译器实现不能给出2
以下情况?
int i = 0;
i = ++i + 1;
std::cout << i << std::endl;
Run Code Online (Sandbox Code Playgroud)
事情是operator=
有两个args.第一个总是i
参考.在这种情况下,评估顺序无关紧要.除了C++ Standard禁忌之外,我没有看到任何问题.
请,不要不考虑这样的情况下,参数的顺序是评价非常重要.例如,++i + i
显然是未定义的.请,只考虑我的情况
i = ++i + 1
.
为什么C++标准禁止这样的表达式?
Rob*_*edy 62
你错误地认为operator=
是一个双参数函数,在函数开始之前必须完全评估参数的副作用.如果是这种情况,那么表达式i = ++i + 1
将具有多个序列点,并且++i
在分配开始之前将被完全评估.但事实并非如此.在内在赋值运算符中评估的是什么,而不是用户定义的运算符.该表达式中只有一个序列点.
的结果的++i
分配(和加法运算符之前),但前被评估副作用不一定施加马上.结果++i + 1
总是相同的i + 2
,因此这是i
作为赋值运算符的一部分赋值的值.结果++i
总是i + 1
如此,因此i
作为增量运算符的一部分被赋值.没有序列点来控制首先分配哪个值.
由于代码违反了"在前一个和下一个序列点之间,标量对象应通过表达式的计算最多修改其存储值一次"的规则,因此行为未定义.但实际上,它可能会先i + 1
或i + 2
先分配,然后分配另一个值,最后程序将继续照常运行 - 没有鼻子恶魔或爆炸式厕所,也没有i + 3
.
CB *_*ley 37
它是未定义的行为,而不是(仅仅)未指定的行为,因为有两个写入i
没有插入序列点.就标准规定而言,按照定义就是这种方式.
该标准允许编译器生成代码,将写入延迟写回存储 - 或从另一个视点,重新排序实现副作用的指令 - 只要它符合序列点的要求,它选择的任何方式.
此语句表达式的问题在于它意味着两次写入而i
没有插入序列点:
i = i++ + 1;
Run Code Online (Sandbox Code Playgroud)
一次写入是原始值i
"加一"的值,另一次写入是"加一"的值.这些写入可以按任何顺序发生,也可以在标准允许的范围内完全爆炸.从理论上讲,这甚至使实现可以自由地并行执行回写,而无需检查同时访问错误.
Joh*_*itb 10
停止,这在C++ 11中得到了很好的定义.它仅在C++ 03中未定义,但C++ 11更灵活.
int i = 0;
i = ++i + 1;
Run Code Online (Sandbox Code Playgroud)
在那一行之后,i
将是2.这个改变的原因是......因为它已经在实践中起作用,并且如果将其定义为在C++ 11的规则中定义它将是更多的工作是不确定的(实际上,现在这种做法更像是一次意外而非故意改变,所以请 不要在你的代码中这样做!).
http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#637
该标准的作者有两种选择:定义行为或将其指定为未定义.
鉴于首先编写此类代码的明显不明智的性质,为其指定结果没有任何意义.人们会想要阻止这样的代码,而不是鼓励它.它对任何事都没有用或没必要.
此外,标准委员会没有任何办法迫使编译器编写者做任何事情.如果他们需要特定的行为,那么该要求可能会被忽略.
也有实际的原因,但我怀疑他们从属于上述一般性考虑.但是对于记录来说,这种表达式和相关类型的任何必需行为都会限制编译器生成代码,分解常见子表达式,在寄存器和内存之间移动对象等的能力.C已被弱可见性所阻碍限制.像Fortran这样的语言很久以前就意识到别名参数和全局变量是一个优化杀手,我相信他们只是禁止它们.
我知道你对一个特定的表达感兴趣,但任何给定结构的确切性质并不重要.预测复杂的代码生成器将会做什么并且语言试图在愚蠢的情况下不需要这些预测并不容易.
该标准的重要部分是:
通过表达式的计算,其存储值最多被修改一次
您可以将值修改两次,一次使用++运算符,一次使用赋值
请注意,您的标准副本已过时,并且在示例的第1和第3代码行中包含已知(且已修复)的错误,请参阅:
和
只是阅读标准这个主题并不容易(这是非常模糊的:(在这种情况下).
例如,它是否(未)定义,未指定,或者通常情况下实际上不仅取决于语句结构,还取决于执行时的内存内容(具体来说,变量值),另一个例子:
++i, ++i; //ok
(++i, ++j) + (++i, ++j); //ub, see the first reference below (12.1 - 12.3)
Run Code Online (Sandbox Code Playgroud)
请看一下(它清晰而准确):
此外,Angelika Langer有一篇关于这个主题的文章(尽管不像前一篇那样清晰):
还有一个俄语讨论(尽管在评论和帖子中有一些明显错误的陈述):