今天我遇到了一些表现出不同行为的代码 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;
}
g ++(4.9.2)给出了这个输出:
len_: -1
len_: 10
因此g ++将参数计算为缓冲区,然后缓冲区(..)本身,然后评估数组运算符的索引参数。直观地说,这对我来说很有意义。
clang(3.7-git)和Visual Studio 2013都给出了:
len_: 10
len_: 10
我想clang和VS2013在它降到缓冲区(..)之前评估所有可能的东西。这对我来说不太直观。
我想我的问题的关键在于这是否是未定义行为的明显案例。
编辑:感谢您清除此问题,并且未指明的行为是我应该使用的术语。
答案 0 :(得分:18)
unspecified behavior len_ = len
,buffer()
另外,但没有指定哪个顺序,但有一个排序,所以评价不能重叠,因此没有未定义的行为。这意味着gcc
,clang
和Visual Studio
都是正确的。另一方面,未经测试的评估允许重叠评估,这可能导致未定义的行为,如下所述。
来自draft C++11 standard部分1.9
[intro.execution] :
[...]调用函数中的每个评估(包括其他函数调用)都没有特别说明 在执行被调用函数体之前或之后对序列进行测序是不确定的 关于被调用函数的执行。[...]
和不确定地排序在此之前有所涉及,并说:
[...]当A或A时,评估A和B都是不确定的 在A或B之前对B或B进行测序之前对其进行测序,但未指定哪一种。 [注意:不确定 有序的评估不能重叠,但可以先执行。 - 后注]
与未经测试的评估不同:
[...]如果A之前没有排序 B和B在A之前未被测序,然后A和B未被测序。 [注:执行无序 评估可以重叠。 - 尾注] [...]
可导致未定义的行为(强调我的):
除非另有说明,否则对个体操作员的操作数和个体的子表达式进行评估 表达式没有排序。 [注意:在执行期间多次计算的表达式中 一个程序,对其子表达式的无序和不确定顺序的评估不一定是 在不同的评估中表现一致。 -end note]一个操作数的值计算 在运算符结果的值计算之前对运算符进行排序。 如果对标量有副作用 对于相同标量对象的另一个副作用或值计算,对象未被排序 使用相同标量对象的值,行为未定义 [...]
Pre 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。本文涵盖了更多complicated case来自&#34; C ++编程语言&#34;第4版。这表明即使是那些具有丰富C ++经验的人也会被绊倒。
答案 1 :(得分:12)
嗯,不,这不是未定义行为的情况。这是一个未指明行为的案例。
未指定是否在len_ = len
之前或之后评估表达式buffer(len+1)
。从您描述的输出中,g ++首先评估buffer(len+1)
,然后clang首先评估len_ = len
。
两种可能性都是正确的,因为这两个子表达式的评估顺序是未指定的。将评估两个表达式(因此行为不符合未定义的条件),但标准未指定顺序。