论坛首页 编程语言技术论坛

Sequence Point (呜呜,综合技术木有人看,于是转过来)

浏览 6867 次
精华帖 (0) :: 良好帖 (6) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-12-12   最后修改:2009-12-16
C++
这是很久的事情了,是当时还在acm集训,为了某题,要写线段树,当时为了省事,就用vector搞了。

C++里的vector很神奇,它能够动态的增长,在拷贝的时候也是COW的。我想,可以用lazy来概括,内存分配基本都是直到用的时候才会去分配。

问题出现在。。。我是递归插入vector的,但是有要递归的为某个vector里面的元素修改他的属性
类似于

vec[parent_idx].left = build_tree(vec, a, b);

其中build_tree会返回一个线段树的root下标。

这代码看似没问题。结果一跑就挂了,而且就是在这个赋值的时候挂掉了。结果就写了一个实例程序测试一下。

#include <iostream>

class Test {
public:
    int& operator[] (int idx) {
        std::cout << "operator[] " << idx << std::endl;
        return value_;
    }

    int func() {
        std::cout << "func()" << std::endl;
        return 1;
    }
private:
    int value_;
};

int main(int argc, char *argv[])
{
    Test t;
    t[0] = t.func();
    
    return 0;
}


结果令我大吃一惊,在我的4.4下,竟然是先求左边的值
mike@mike-laptop% ./a.out 
operator[] 0
func()


不过还是不甘心,因为g++bug还是有可能的-,-.于是跑到学校的solaris上跑。上面是gcc3.4.4,结果却是反过来的。@_@ 后来经过一个在微软写编译器的大牛的点拨,才知道这是怎么回事。

这种东西叫做sequence point ambiguity。首先说什么是sequence point.imperative programming language的赋值,输出,一切一切会改变全局状态的操作叫做side effect,(这大家都应该熟悉),很明显side effect的顺序是很重要的。所以为了定义side effect的顺序,就有了sequence point,每个sequence point的前面保证了所有的side effect都能发生,每个sequence point后面的side effect都没有发生。

在C中所有的= operator都不是sequence point。所以你写一个i = i++;不会有人知道会发生什么。因为等号两边都有side effect。我个人猜测C++为了和C兼容,才造成上面的错误,由于我们把[]重载了,那么[]会发生side effect,右面的func明显也是side effect,所以上面程序的输出结果,是不确定的!

再来看我那个最开始的线段树,左面是赋值,有side effect,右面有对vector的push_back也是side effect。在gcc4.4中,左面先发生,于是vector很好的找到一个地址,给我返回,然后在执行右面,可惜,执行右面的时候内存不够了,于是vector会重新分配内存,把前面的内存free掉,然后返回了我一个值,可是这个值我是绝对没法赋给原来那个地址了,因为那地址已经失效了。相当于左边给了我一张空头支票。。。。

于是。。。。只好用一个中间变量重写了。。T_T
不过这个东西的确很隐蔽,下次一定要注意
   发表时间:2009-12-15  
试了一下,在VC++中是先计算右边的,其实这种情况可以避免的,只需要将复杂的复合语句拆分即可,如此例中:
int i = t.func();
t[0] = i;

这种难以理解,又依赖语言特性的代码,应该尽量避免.
0 请登录后投票
   发表时间:2009-12-15  
f(g(), h()) 中,g() 和 h() 的求值顺序也是未指明(unspecified)的,这点需要注意。
0 请登录后投票
   发表时间:2009-12-15   最后修改:2009-12-15
Solstice 写道
f(g(), h()) 中,g() 和 h() 的求值顺序也是未指明(unspecified)的,这点需要注意。

你确定?
求sample,我查了wiki的,wiki上是说", operator is a sequence point"的
至少g,h的顺序是能保证的。

0 请登录后投票
   发表时间:2009-12-15  
dicom 写道
试了一下,在VC++中是先计算右边的,其实这种情况可以避免的,只需要将复杂的复合语句拆分即可,如此例中:
int i = t.func();
t[0] = i;

这种难以理解,又依赖语言特性的代码,应该尽量避免.


但是的确不是portable的,而且也是标准里强调的unportable的做法。
换句话说编译器爱怎么实现就怎么实现。。。

PS 你可以试试debug和release的结果是不是一样的,在Sun CC 12里,-O2和-O0的结果不一样,-O2会先左后右,-O0不会。然而,在我那个会挂掉的程序里面,却没有这种现象,由此可见Sun CC对这种东西做了优化的,结果是由优化参数而决定的。
0 请登录后投票
   发表时间:2009-12-15   最后修改:2009-12-15
Solstice 写道
f(g(), h()) 中,g() 和 h() 的求值顺序也是未指明(unspecified)的,这点需要注意。

嗯查了一下,的确是没有规定的。

我的但忽然觉得我的那个解决方案还是对的。。。。-,-因为不是换成了,operator了。。。。。刚才放出来一写才发现
const test& operator=(const test& rhs)....... 明明是重载成了函数。。。。-,-不是成了参数。。。汗。。。

于是编辑了。。。
0 请登录后投票
   发表时间:2009-12-15  
mikeandmore 写道
Solstice 写道
f(g(), h()) 中,g() 和 h() 的求值顺序也是未指明(unspecified)的,这点需要注意。

嗯查了一下,的确是没有规定的。

我的但忽然觉得我的那个解决方案还是对的。。。。-,-因为不是换成了,operator了。。。。。刚才放出来一写才发现
const test& operator=(const test& rhs)....... 明明是重载成了函数。。。。-,-不是成了参数。。。汗。。。

于是编辑了。。。

重载 operator= 不是办法,因为它其实有两个参数,求值顺序仍然是 unspecified.
0 请登录后投票
   发表时间:2009-12-15  
Solstice 写道
mikeandmore 写道
Solstice 写道
f(g(), h()) 中,g() 和 h() 的求值顺序也是未指明(unspecified)的,这点需要注意。

嗯查了一下,的确是没有规定的。

我的但忽然觉得我的那个解决方案还是对的。。。。-,-因为不是换成了,operator了。。。。。刚才放出来一写才发现
const test& operator=(const test& rhs)....... 明明是重载成了函数。。。。-,-不是成了参数。。。汗。。。

于是编辑了。。。

重载 operator= 不是办法,因为它其实有两个参数,求值顺序仍然是 unspecified.

你是说运行时的时候有两个参数?
这个倒是真的,但是标准似乎规定的是function call operator是sequence。
也就是说a.f(b)的parse顺序是b->()->a (inside f),所以()应该是在a,b之间的,所以a.f和b的顺序是确定的。
0 请登录后投票
   发表时间:2009-12-15  
mikeandmore 写道
Solstice 写道
mikeandmore 写道
Solstice 写道
f(g(), h()) 中,g() 和 h() 的求值顺序也是未指明(unspecified)的,这点需要注意。

嗯查了一下,的确是没有规定的。

我的但忽然觉得我的那个解决方案还是对的。。。。-,-因为不是换成了,operator了。。。。。刚才放出来一写才发现
const test& operator=(const test& rhs)....... 明明是重载成了函数。。。。-,-不是成了参数。。。汗。。。

于是编辑了。。。

重载 operator= 不是办法,因为它其实有两个参数,求值顺序仍然是 unspecified.

你是说运行时的时候有两个参数?
这个倒是真的,但是标准似乎规定的是function call operator是sequence。
也就是说a.f(b)的parse顺序是b->()->a (inside f),所以()应该是在a,b之间的,所以a.f和b的顺序是确定的。

a sample
#include <iostream>

class test {
public:
    // const test& operator=(const test& rhs) {
    //     return (*this);
    // }
};

class test_run {
    test data;
public:
    test& operator[](int idx) {
        std::cout << "[]" << std::endl;
        return data;
    }
    const test& func() {
        std::cout << "func" << std::endl;
        return data;
    }
};

int main(int argc, char *argv[])
{
    test_run r;
    r[0] = r.func();
    return 0;
}


mike@mike-laptop% ./a.out    
[]
func


uncomment以后
mike@mike-laptop% ./a.out    
func
[]


嗯,由于我的编译器(g++4.4)的sequence point ambigious比较严重,所以还是能部分说明问题的
0 请登录后投票
   发表时间:2009-12-15  
mikeandmore 写道
Solstice 写道
f(g(), h()) 中,g() 和 h() 的求值顺序也是未指明(unspecified)的,这点需要注意。

嗯查了一下,的确是没有规定的。

我的但忽然觉得我的那个解决方案还是对的。。。。-,-因为不是换成了,operator了。。。。。刚才放出来一写才发现
const test& operator=(const test& rhs)....... 明明是重载成了函数。。。。-,-不是成了参数。。。汗。。。

于是编辑了。。。


利用operator重载去解决这种问题,非常隐晦,难以理解,其实就用简单的语法语句,显式的将执行顺序定义清楚(如果执行顺序相关),简单而清晰:
int i = g();
int j = h();
f(i, j);

谁能理解 f(i++, ++i, ++i, i++)这种垃圾代码?
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics