在将一些C代码从Microsoft Visual Studio移植到
gcc时,我遇到了一个奇怪的bug,我最终归结为:
- #include <iostream>
- using namespace std;
- class Foo {
- public:
- int data;
- Foo(int i) : data(i)
- {
- cout << "Foo constructed with " << i << endl;
- }
- Foo(const Foo& f) : data(f.data)
- {
- cout << "copy ctor " << endl;
- }
- Foo(const Foo&& f) : data(f.data)
- {
- cout << "move ctor" << endl;
- }
- ~Foo()
- {
- cout << "Foo destructed with " << data << endl;
- }
- };
- int Bar(Foo f)
- {
- cout << "f.data = " << f.data << endl;
- return f.data * 2;
- }
- int main()
- {
- Foo f1(10);
- Foo f2(Bar(std::move(f1)));
- }
如果我使用Microsoft Visual Studio 2015社区编译和运行上述代码,我得到以下输出:
- Foo constructed with 10
- move ctor
- f.data = 10
- Foo destructed with 10
- Foo constructed with 20
- Foo destructed with 20
- Foo destructed with 10
但是,如果我使用gcc 6.1.1和–std = c 14来编译和运行代码,我得到这个输出:
- Foo constructed with 10
- move ctor
- f.data = 10
- Foo constructed with 20
- Foo destructed with 10
- Foo destructed with 20
- Foo destructed with 10
gcc调用f的析构函数,Bar()的参数在Bar()返回后,而msvc在返回之前调用析构函数(显然),或至少在构造函数f2之前.根据C标准,什么时候f被破坏?
解决方法
他们没事这取决于.标准似乎不太明确.
从[expr.call]/4(这个措辞可以追溯到C98);
The lifetime of a parameter ends when the function in which it is defined returns. The initialization and destruction of each parameter occurs within the context of the calling function.
和CWG#1880;
WG decided to make it unspecified whether parameter objects are destroyed immediately following the call or at the end of the full-expression to which the call belongs.
g(和clang)和MSVC的行为都是正确的,实现可以自由选择一种方法.
这一切都说,如果你的代码依赖于这个顺序,我会改变它,使得顺序更确定 – 如你所看到的,它导致了微妙的错误.
这个行为的简化例子是:
- #include <iostream>
- struct Arg {
- Arg() {std::cout << 'c';}
- ~Arg() {std::cout << 'd';}
- Arg(Arg const&) {std::cout << 'a';}
- Arg(Arg&&) {std::cout << 'b';}
- Arg& operator=(Arg const&) {std::cout << 'e'; return *this;}
- Arg& operator=(Arg&&) {std::cout << 'f'; return *this;}
- };
- void func(Arg) {}
- int main() {
- (func(Arg{}),std::cout << 'X');
- std::cout << std::endl;
- }
Clang和g都产生cXd和MSVC生成cdX.