在将一些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.