序列点歧义,未定义的行为?

Fre*_*son 20 c++ unspecified-behavior

今天我遇到了一些代码,这些代码在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] = '\0';
    cout << "len_: " << len_ << endl;
}
Run Code Online (Sandbox Code Playgroud)

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

len_: -1
len_: 10
Run Code Online (Sandbox Code Playgroud)

因此g ++将参数计算为缓冲区,然后缓冲区(..)本身,然后评估数组运算符的索引参数.直观地说,这对我来说很有意义.

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

len_: 10
len_: 10
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:感谢您清除此问题,未指明的行为是我应该使用的术语.

Sha*_*our 18

这是未指定的行为,对于正文的执行len_ = len不确定的顺序buffer(),这意味着一个将在另一个之前执行但是没有指定哪个顺序但是有一个排序因此评估不能重叠因此没有未定义的行为.这意味着gcc,clang并且Visual Studio都是正确的.另一方面,未经测试的评估允许重叠评估,这可能导致未定义的行为,如下所述.

草案C++ 11标准部分1.9 [intro.execution]:

[...]调用函数中的每个评估(包括其他函数调用)在执行被调用函数体之前或之后没有特别排序,对被调用函数的执行进行不确定的排序.[ ...]

不定测序此之前,覆盖了一点,说:

[...]评估A和B是在B之前对A进行测序或在A之前对B进行测序时进行不确定测序,但未指定哪一种.[注意:不确定顺序的评估不能重叠,但可以先执行. - 尾注]

这与未经测试的评估不同:

[...]如果A在B之前没有排序,B在A之前没有排序,那么A和B都没有排序.[注意:未经测试的评估的执行可能会重叠. - 注意事项] [...]

这会导致不确定的行为(强调我的):

除非另有说明,否则对单个运算符的操作数和单个表达式的子表达式的评估是不确定的.[注意:在程序执行期间不止一次评估的表达式中,不需要在不同的评估中一致地执行对其子表达式的未序列和不确定顺序的评估.-end note]运算符操作数的值计算在运算符结果的值计算之前排序.如果对标量对象的副作用相对于同一标量对象的另一个副作用或使用相同标量对象的值的值计算未被排序,则行为未定义 [...]

前C++ 11

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

[...]函数入口和函数出口的序列点(如上所述)是被评估的函数调用的特征,无论调用函数的表达式的语法如何.

坚持评估的顺序

根据您的观点和期望,每个编译器做出的不同选择可能看起来不直观.确定评估顺序的主题是EWG问题158的主题:N4228精炼表达评估顺序的惯用语C++,正在考虑用于C++ 17但是基于对该主题的民意调查的反应似乎存在争议.本文介绍一种更复杂的情况下,"C++程序设计语言"第4版.这表明即使那些具有丰富C++经验的人也会被绊倒.


Pet*_*ter 12

嗯,不,这不是未定义行为的情况.这是一种未指明的行为.

未指定表达式len_ = len是在之前还是之后进行评估buffer(len+1).从您描述的输出中,g ++ buffer(len+1)首先求值,然后clang求值len_ = len.

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