序列点模糊,未定义的行为



今天我发现一些代码在clang++(3.7-git)、g++(4.9.2)和Visual Studio 2013。经过一些还原我想出了一个突出问题的片段:

#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] = '';
    cout << "len_: " << len_ << endl;
}

g++(4.9.2)给出以下输出:

len_: -1
len_: 10

因此g++将参数求值为buffer,然后将buffer(..)本身求值,然后将索引参数求值为数组运算符。凭直觉,这对我来说是有意义的。

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

len_: 10
len_: 10

我想clang和VS2013在进入缓冲区(..)之前会评估所有可能的情况。这对我来说不太直观。

我想我问题的要点是,这是否是一个明确的未定义行为的案例。

编辑:谢谢你澄清这一点,我本应该用未指明的行为这个词。

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

根据C++11标准草案1.9[intro.execution]:

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

并且不确定排序在此之前被覆盖,并说:

[…]当任一A在B之前测序,或B在A之前测序,但未指明是哪一个。[注:不确定顺序评估不能重叠,但两者都可以先执行--尾注]

这与未排序的评估不同

[…]如果A之前未测序B和B在A之前未测序,则A和B未测序。[注:未排序的执行评估可能会重叠--尾注][…]

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

除非另有说明,否则单独运算符的操作数和单独运算符的子表达式的求值表达式是无序列的。[注意:在执行过程中多次求值的表达式中对于一个程序,其子表达式的无序列和不确定序列的求值不需要在不同的评估中始终如一地执行--尾注]在运算符的结果的值计算之前对运算符进行排序如果标量上有副作用对象相对于同一标量对象上的另一个副作用或值计算未排序使用相同标量对象的值,行为是未定义的[…]

C++11之前

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

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

确定评估顺序

根据您的观点和期望,每个编译器做出的不同选择可能看起来不直观。确定评估顺序的主题是EWG第158期的主题:N4228习语C++的精炼表达式评估顺序,该主题正在考虑用于C++17,但根据对该主题的民意调查的反应,似乎存在争议。本文涵盖了《C++编程语言》第4版中一个更为复杂的案例。这表明,即使是那些在C++方面有丰富经验的人也可能会被绊倒。

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

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

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

相关内容

  • 没有找到相关文章

最新更新