int i = f1() * f2() 的未定义行为

Posted

技术标签:

【中文标题】int i = f1() * f2() 的未定义行为【英文标题】:undefined behavior for int i = f1() * f2() 【发布时间】:2021-08-10 23:33:09 【问题描述】:

我很困惑为什么这会导致未定义的行为。让我先复制粘贴教科书上的解释,然后展示我自己完美运行的代码和程序。

Precedence 指定操作数的分组方式。它什么也没说 关于计算操作数的顺序。在大多数情况下, 该顺序在很大程度上未指定。在以下表达式中* int i = f1() * f2();: *我们知道必须先调用 f1f2 才能进行乘法运算。毕竟是他们的 结果相乘。然而,我们无从得知 f1 是否会在 f2 之前被调用,反之亦然。对于运营商 不指定评估顺序,表达式会出错 引用和更改同一个对象。这样做的表达式有 未定义的行为(第 2.1.2 节,第 36 页)。举个简单的例子,

-- C++ 入门 - Stanley B. Lippman 第 193 页

所以,我尝试通过编写自己的代码来应用它,但我从来没有得到未定义的行为?谁能解释一下这是什么意思?

#include <iostream>

using std::cout;
using std::endl;

int f1()  return (5 + 5 * 4 / 2 - 3);  // 12
int f2()  return (10 + 2 * 10 / 2 - 5);  // 15

int main()

    int i = f1() * f2();
    cout << i << endl;
    return 0;

【问题讨论】:

您的两个函数都不会修改同一个变量,因此它们的求值顺序无关紧要。 it is an error for an expression to refer to and change the same object 好吧,f1f2 都不会改变同一个对象。 第一:您希望如何检测未定义的行为?也就是说,如果您遇到未定义的行为,您希望看到的输出或程序行为会向您表明这一点吗?第二:为什么您认为您的示例代码会调用未定义的行为? f1f2 都不会修改 i 或其他任何与此相关的内容。 您可以通过实验验证 UB 的存在,但永远不能absence UB。 (发布的代码是可以的,但你不能仅仅通过运行它来证明)。 顺便说一句,李普曼在这里大错特错。 f1() 和 f2() 访问和修改同一个变量不是未定义的行为。未指定顺序,但任一顺序均已明确定义。您可以在 f2() 之后调用 f1() 或在 f1() 之后调用 f2(),但没有别的。 【参考方案1】:

你搞错了。作者的意思是如果订单很重要,则未指定。在您的情况下,评估顺序无关紧要。其实这个函数也可以是constexpr。但如果你有这样的事情:

int i = 0;

int f1()  return (i++) * 3; 
int f2()  return (i++) * 4; 

int main() 
    int a = f1() + f2();

现在,如果先调用f1,则结果为4。如果先调用f2,则结果为3。因此,未指定。


我从来没有遇到过未定义的行为?

你不能仅仅通过运行程序就知道这一点。

【讨论】:

i没有初始化的情况下,结果4和3分别如何? @ProgrammingRage 静态变量默认初始化为零。在这个例子中,i 是一个(全局)静态变量,因此它的值一开始就为零。 好的,所以我在这里试用了您的代码:imgur.com/gh2ke8C 显然 f1() 返回了 0,f2() 返回了 4,不知何故 a 是 18???? @ProgrammingRage 因为您多次调用了这些函数。使用您的调试器逐步查看发生了什么。见What is a debugger and how can it help me diagnose problems? @ProgrammingRage 欢迎来到具有副作用的函数空间(也就是非纯函数)【参考方案2】:

你的代码没问题。

表达式引用和更改同一个对象是错误的

(我的粗体字)

您不会更改表达式中的任何对象,因此该规则不适用。

以下是该规则何时适用的示例:

int a = 42;
int i = a++ * a++;

请注意,如果更改发生在函数中,它将适用:

int a = 42;
int foo() return a++;
int i = foo() * foo();

这是因为 UB 仅在 two accesses to an object are unsequenced relative to each other 时发生,即可以以任何顺序发生,包括并行发生。这不一定意味着“并行线程”,也可能意味着“单线程,但执行任务的处理器指令可能是交错的”。

但是同一线程上的两个函数调用不能并行发生(并且它们的指令不能交错)。相反,在这种情况下,它们是不确定排序的,即一个紧接着一个,但不确定哪个是第一个。

还要注意

从 C++17 开始不再适用。

【讨论】:

i = (a=2) * (a=3) 也会是这样的例子吗? (因为,如果是的话,那我觉得比i = i++ * i++更简单的例子@ 抱歉,我多次阅读您的回答,但当您说indeterminately sequenced 但不能交错时,我不明白。一方面你是说顺序是不确定的,可以按任何顺序发生,但不能混合??? @ProgrammingRage he(?) 基本上表示函数是同步调用的。 (即控件在第一个函数调用返回之前不会转到另一个函数调用)。 @ProgrammingRage 这不是错误,而是功能! @ProgrammingRage ***.com/a/2934909/10147399【参考方案3】:

来自 C++ 标准草案:

3.64 [defns.undefined] 未定义的行为

本文档没有要求的行为 [注 1:未定义的行为可能是 当本文档省略任何明确的行为定义时预期 或者当程序使用错误的构造或错误的数据时。 允许的未定义行为范围从忽略情况 完全具有不可预测的结果,在翻译过程中表现得很好 或以文件化方式执行程序的特征 环境(无论是否发布诊断消息),以 终止翻译或执行(发出 诊断信息)。许多错误的程序结构不会产生 未定义的行为;他们需要被诊断出来。评价一个 常量表达式 ([expr.const]) 从不显式地表现出行为 在 [intro] 到 [cpp] 中指定为未定义。 ——尾注]

因此,几乎所有事情都是可能的,甚至是给定实现的可预测行为。

但是,您的程序没有显示任何 UB。 f1 和 f2 没有任何边界效应,因此它们的评估顺序没有影响。

【讨论】:

是的,我知道f1()f2() 没有任何UB。但我试图了解什么代码会导致 UB。

以上是关于int i = f1() * f2() 的未定义行为的主要内容,如果未能解决你的问题,请参考以下文章

斐波那契数列

Codeforces 1077(F1+F2) DP 单调队列

二分AtcoderE - Max Min

php--常见算法2

在C语言中如何输出空格?

如何构建MIPS交叉编译工具链