【问题标题】:Sequence Point ambiguity, undefined behavior?序列点歧义,未定义的行为?
【发布时间】:2015-06-13 08:20:25
【问题描述】:

今天我遇到了一些在 clang++ (3.7-git), g++ (4.9.2) 和 Visual Studio 2013。经过一些缩减 我想出了这个突出问题的 sn-p:

#include <iostream>
using namespace std;

int len_ = -1;

char *buffer(int size_)
{
    cout << "len_: " << len_ << endl;
    return new char[size_];
}

int main(int argc, char *argv[])
{
    int len = 10;
    buffer(len+1)[len_ = len] = '\0';
    cout << "len_: " << len_ << endl;
}

g++ (4.9.2) 给出这个输出:

len_: -1
len_: 10

所以 g++ 将参数计算为缓冲区,然后是 buffer(..) 本身,然后将索引参数计算为数组运算符。直觉上这对我来说很有意义。

clang (3.7-git) 和 Visual Studio 2013 都给出:

len_: 10
len_: 10

我想 clang 和 VS2013 在它下降到缓冲区(..)之前评估所有可能的东西。这对我来说不太直观。

我想我的问题的要点是这是否是未定义行为的明显案例。

编辑:感谢您澄清这一点,未指明的行为是我应该使用的术语。

【问题讨论】:

  • 如果有人做了“buffer(len+1)[len_ = len] = '\0';”我会告诉他们改变它:)

标签: c++ unspecified-behavior


【解决方案1】:

这是unspecified behaviorlen_ = len相对于buffer()的主体的执行是不确定的的,这意味着一个将在另一个之前执行但没有指定哪个顺序但有一个顺序,因此评估不能重叠,因此没有未定义的行为。这意味着gccclangVisual Studio 都是正确的。另一方面,未排序的评估允许重叠评估,这可能导致如下所述的未定义行为。

来自draft C++11 standard 部分1.9 [intro.execution]

[...]调用函数(包括其他函数调用)中没有特别指定的每个求值 在被调用函数的主体执行之前或之后排序 不确定地排序为 关于被调用函数的执行。9[...]

and indeterminately sequenced 在此之前稍作介绍并说:

[...]当 A 在 B 之前排序或 B 在 A 之前排序,但未指定哪个。 [注:不确定 顺序评估不能重叠,但可以先执行。 ——尾注]

这不同于无序评估

[...]如果 A 之前没有排序 B 和 B 在 A 之前没有排序,那么 A 和 B 没有排序。 [注:未排序的执行 评价可以重叠。 ——尾注][...]

这可能导致未定义的行为(强调我的):

除非另有说明,否则单个运算符的操作数和单个运算符的子表达式的求值 表达式是无序的。 [注意:在执行期间多次评估的表达式中 对于程序,其子表达式的未排序和不确定排序的评估不需要 在不同的评价中表现一致。 —尾注] 一个操作数的值计算 运算符在运算符结果的值计算之前排序。 如果对标量产生副作用 相对于同一标量对象的另一个副作用或值计算,对象是无序的 使用相同标量对象的值,行为未定义[...]

C++11 之前

Pre C++11 子表达式的求值顺序也未指定,但它使用sequence points 而不是排序。在这种情况下,函数入口和函数出口处有一个序列点,可确保没有未定义的行为。来自1.9部分:

[...]序列指向函数入口和函数出口 (如上所述)是评估后的函数调用的特征,无论调用 功能可能是。

确定评估顺序

根据您的观点和期望,每个编译器做出的不同选择可能看起来不直观。确定评估顺序的主题是 EWG issue 158: N4228 Refining Expression Evaluation Order for Idiomatic C++ 的主题,它正在考虑用于 C++17,但似乎有争议 based on the reactions to a poll on the subject。这篇论文涵盖了更多来自“C++ 编程语言”第 4 版complicated case。这表明即使是那些在 C++ 方面有丰富经验的人也会被绊倒。

【讨论】:

    【解决方案2】:

    嗯,不,这不是未定义行为的情况。这是一个未指明行为的案例。

    未指定表达式len_ = len 将在buffer(len+1) 之前还是之后计算。根据您描述的输出,g++ 首先评估 buffer(len+1),而 clang 首先评估 len_ = len

    这两种可能性都是正确的,因为这两个子表达式的求值顺序是未指定的。两个表达式都将被评估(因此行为不符合未定义的条件),但标准没有指定顺序。

    【讨论】:

      猜你喜欢
      • 2023-03-24
      相关资源
      最近更新 更多