vis*_*ill 1 c++ order-of-execution c++14 c++17
这可能是“ c++ 101”级别的问题,但有一些相关的痛苦,所以我会要求评论以保持自信。
我有一些遗留代码支持返回到c++11及之前的这一行:
iTmp = ~iTmp++;
Run Code Online (Sandbox Code Playgroud)
在代码更改为c++17之前,此方法 100% 有效。更改为c++17后,它会计算出不同的值,但并非总是如此。这取决于该行在代码中的位置,是先执行 2 的补码还是先执行增量。
另外,如果您尝试强制使用括号,则会出现编译器错误:
iTmp = (~iTmp)++;
error C2105: '++' needs l-value
Run Code Online (Sandbox Code Playgroud)
这对我来说很有意义,因为 2 的补码结果不是中间变量,因此 ++ 没有任何可操作的内容。
我的研究告诉我
我的评价正确吗?
对我来说,一个非常微妙的区别是前面的代码无效,但这一行可以:
if ( ++iTmp > 0)
Run Code Online (Sandbox Code Playgroud)
我没想到升级到c++17会导致这个微妙的错误,但我不得不将代码更改为以下内容来解决问题:
iTmp = ~iTmp;
iTmp++;
Run Code Online (Sandbox Code Playgroud)
use*_*522 10
在代码更改为 c++17 之前,此方法 100% 有效。
在 C+17 之前,它具有未定义的行为,因为有两个副作用iTmp(一个递增,一个分配),并且=后递增都没有暗示这两个副作用之间的任何顺序。
看来你很幸运,编译器总是以你想要的方式编译它。
更改为 c++17 后,它会计算出不同的值,但并非总是如此。
不,从 C++17 开始,它被明确定义:首先完全评估赋值的右侧,即iTmp首先递增。但是,iTmp++会生成 的前一个值iTmp,并且该值计算也会在赋值的副作用之前排序。因此,它的行为与 相同iTmp = ~iTmp,只是如果iTmp是有符号的并且因增量而溢出,它也将具有 UB。
这取决于该行在代码中的位置,是先执行 2 的补码还是先执行增量。
我不知道你在这里的意思。编译器不再有选择。只有一个命令是正确的。
另外,如果您尝试强制使用括号,则会出现编译器错误:
是的,因为您无法增加右值(由~运算符生成)。
这对我来说很有意义,因为 2 的补码结果不是中间变量,因此 ++ 没有任何可操作的内容。
C++ 中不存在术语“中间变量”。原因是该表达式不是左值。这只是语言规范的某种任意选择。它可以避免犯错误。
如果(~iTmp)++允许,则意味着您首先创建一个值为 的新整数对象~iTmp,该对象仅临时存在,然后递增该临时对象。表达式之后,临时对象再次被销毁。这样的行为可能不是有意的,因为临时对象的值以后并不重要,如果您只想在表达式中销毁它之前读取它的值,那么您可以改为编写(~iTmp) + 1,这样更清晰、更少令人困惑。
这是“执行顺序”失败。
是的。
运算符优先级不参与其中,因为两个运算符都是二元的。
运算符优先级是相关的,因为您需要能够解析 is~iTmp++和~(iTmp++)not (~iTmp)++。请注意,这只是关于解析。它不影响评估顺序。我不知道为什么您认为二元运算符在某种程度上并不意味着运算符优先级的相关性。
二进制补码不是就地操作,因此它不会更改 iTmp,而是返回一个值。
“补码”是一组位中带符号整数值的表示选择。运算符~只是补码,整数类型的表示选择是什么并不重要。也是的,~不修改iTmp。
遗留代码的工作靠运气 - 可能是由于编译器错误或错误总是强制执行运算符评估顺序。
是的,这靠运气。但编译器根本不应该受到责备。代码根本就是错误的。
对我来说,一个非常微妙的区别是前面的代码无效,但这一行可以:
该行中只有一处修改iTmp。了解和理解 C++ 中计算规则的顺序以及它们何时导致未定义的行为(例如,如果两个副作用或一个副作用和值计算未排序)至关重要。
有关评估规则顺序的参考,请参阅cpprference ,尽管作为介绍可能有点让人不知所措。任何 C++ 入门书籍都应该以更平易近人的方式介绍评估规则的基本顺序。否则,C++ 新手最终会编写危险的代码,如您的示例所示。
我没想到升级到 c++17 会导致这个微妙的错误,但我不得不将代码更改为以下内容来解决问题:
是的,这很好,并且不违反应遵循的一般规则:在每个表达式中最多修改每个对象一次。