有问题的 C 代码,由 Bjarne Stroustrup 在《C 编程语言》第四版中提供C 编程语言”使用函数链来修改字符串:
<code class="cpp">void f2() { std::string s = "but I have heard it works even if you don't believe in it"; s.replace(0, 4, "").replace(s.find("even"), 4, "only").replace(s.find(" don't"), 6, ""); assert(s == "I have heard it works only if you believe in it"); }</code>
此代码演示了如何链接 update() 操作来更改字符串 s。然而,据观察,该代码在不同的编译器(例如 GCC、Visual Studio 和 Clang)中表现出不同的行为。
虽然代码可能看起来很简单,但它涉及未指定的顺序求值,特别是对于涉及函数调用的子表达式。虽然它不会调用未定义的行为(因为所有副作用都发生在函数调用内),但它确实表现出未指定的行为。
关键问题是子表达式的求值顺序,例如 s.find( "even") 和 s.find(" don't") 没有明确定义。这些子表达式可以在初始 s.replace(0, 4, "") 调用之前或之后进行求值,这可能会影响结果。
如果我们检查代码片段的求值顺序:
s.replace(0, 4, "").replace(s.find("even"), 4, "only").replace(s.find(" don't"), 6, "");
我们可以看到以下子表达式是不确定排序的(由括号中的数字表示):
每对括号内的表达式都是有序的(例如,2 在 3 之前),但它们可以按彼此不同的顺序进行计算。具体来说,不确定性存在于表达式 1 和 2 之间以及表达式 1 和 4 之间。
观察到的编译器行为差异可以归因于每个表达式选择的不同评估顺序编译器。在某些情况下,replace() 调用的计算方式会导致预期的行为,而在其他情况下,计算顺序会以意外的方式更改字符串。
为了说明这一点,请考虑以下情况:
需要注意的是,此代码不调用未定义的行为。未定义的行为通常涉及访问未初始化的变量或尝试访问其边界之外的内存。在这种情况下,所有副作用都发生在函数调用内,并且代码不会访问无效的内存位置。
但是,代码确实表现出未指定的行为,这意味着C 标准没有定义子表达式的求值。这可能会导致不同的编译器产生不同的结果,甚至同一程序的不同运行。
C 标准委员会已认识到此问题并提出更改以细化表达式求值顺序惯用语 C .对 C 20 中的 [expr.call]p5 的拟议更改指定“后缀表达式在表达式列表中的每个表达式和任何默认参数之前排序”,这将消除此代码中未指定的行为。
以上是'C 编程语言”中的函数链是否表现出未指定的行为?的详细内容。更多信息请关注PHP中文网其他相关文章!