c++ - 为什么 i = v[i++] 未定义?

标签 c++ language-lawyer

来自 C++ (C++11) 标准的第 1.9.15 节讨论了评估的顺序,是以下代码示例:

void g(int i, int* v) {
    i = v[i++]; // the behavior is undefined
}

如代码示例中所述,行为未定义。

(注意:另一个问题的答案与构造稍有不同 i + i++, Why is a = i + i++ undefined and not unspecified behaviour , 可能 在这里适用:答案本质上是行为是出于历史原因未定义,并非出于必要。但是,该标准似乎暗示了一些理由未定义 - 请参阅下面的引用。此外,该链接问题表明同意行为应该未指定,而在这个问题中,我要问为什么行为没有明确指定。)

标准对未定义行为给出的推理如下:

If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.

在此示例中,我认为子表达式 i++ 将在 子表达式 v[...] 被评估之前被完全评估,并且子表达式求值的resulti(在增量之前),但是ivalue> 是该子表达式被完全评估后的增量值。我认为在这一点上(在子表达式 i++ 已被完全评估之后),评估 v[...] 发生,然后是赋值 i = ....

因此,虽然 i 的递增是没有意义的,但我还是认为应该定义

为什么会出现这种未定义的行为?

最佳答案

I would think that the subexpression i++ would be completely evaluated before the subexpression v[...] is evaluated

但是为什么你会这么认为呢?

这段代码是 UB 的一个历史原因是允许编译器优化在序列点之间的任意位置移动副作用。序列点越少,优化的潜在机会越多,但程序员越困惑。如果代码说:

a = v[i++];

标准的意图是发出的代码可以是:

a = v[i];
++i;

这可能是两条指令,其中:

tmp = i;
++i;
a = v[tmp];

会超过两个。

ai 时,“优化代码”会中断,但标准仍然允许优化,通过说原始的行为当 ai 时,代码未定义。

标准很容易说 i++ 必须按照您的建议在分配之前进行评估。然后行为将被完全定义,优化将被禁止。但这不是 C 和 C++ 做生意的方式。

还要注意,在这些讨论中提出的许多示例比一般情况下更容易判断周围存在 UB。这导致人们说应该定义行为并且禁止优化是“显而易见的”。但请考虑:

void g(int *i, int* v, int *dst) {
    *dst = v[(*i)++];
}

这个函数的行为是在 i != dst 时定义的,在这种情况下你会想要你能得到的所有优化(这就是 C99 引入 restrict 的原因>,以允许比 C89 或 C++ 进行更多的优化)。为了给您优化,i == dst 时的行为未定义。 C 和 C++ 标准在别名方面走得很好,介于程序员不期望的未定义行为和禁止在某些情况下失败的理想优化之间。 SO 上关于它的问题数量表明,提问者更喜欢少一点优化和多一点定义的行为,但要划清界限仍然并不简单。

除了行为是否完全定义之外,还有一个问题是它应该是 UB,还是仅仅是与子表达式相对应的某些明确定义的操作的未指定执行顺序。 C 选择 UB 的原因完全与序列点的概念有关,并且编译器实际上不需要知道修改对象的 值的概念,直到下一个序列点。因此,与其通过说“该”值在某个未指定的点发生变化来限制优化器,标准只是说(解释一下):(1)任何依赖于下一个序列点之前修改对象的值的代码,都有UB; (2) 任何修改被修改对象的代码都有 UB。其中,“修改的对象”是自子表达式评估的一个或多个合法顺序中的最后一个序列点以来被修改的任何对象。

其他语言(例如 Java)一路走来,完全定义了表达式副作用的顺序,因此肯定有反对 C 方法的案例。 C++ 就是不接受这种情况。

关于c++ - 为什么 i = v[i++] 未定义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13744507/

相关文章:

c++ - 具有多个目标的 Makefile 并创建一个具有可执行文件的目录

c++ - 使用 C++ 模板时出现错误 C2955 和错误 C2244

c++ - 了解用于运算符重载的 C++03 标准语法

c - 强制转换不完整的结构指针是否是未定义的行为?

c++ - 显式模板特化的语法

c++ - 将节点附加到 LinkedList

c++ - 如何使用 libcurl 下载 utf-8 编码的网页,同时保留编码?

c++ - 通过构造函数初始化成员函数

c++ - 强制数组衰减(因为没有更好的标题)

c++ - 试图理解 C++ 标准中的 [class.qual]/2