局部变量销毁与返回的可观测顺序差异及相关技术问询
问题描述
我有一个在GCC和Clang中表现不同的程序,简化后的最小可复现示例如下:
struct A { int i; }; struct Finisher { A & a; ~Finisher() { a.i = 1; } }; A f() { A a{0}; Finisher fr{a}; return a; } int main() { std::cout << f().i; }
该程序在GCC中输出0,在Clang中输出1。从程序行为来看,Clang似乎先执行~Finisher()将a.i设为1,再执行return a;而GCC则先返回a.i=0,再执行fr的析构函数。
请问该程序是否存在未定义行为?能否修改Finisher,使其总能在返回a前修改a的值以返回1?
解答
是否存在未定义行为?
不存在未定义行为,但这属于未指定行为。
C++标准仅要求函数内的局部对象必须在函数返回给调用方之前完成析构,但并没有强制规定返回值的初始化(包括拷贝、移动构造或返回值优化)与非返回值局部对象的析构之间的具体执行顺序。GCC和Clang的不同输出均符合标准,只是各自选择了合法的执行顺序而已。
如何修改Finisher确保返回1?
可以通过给Finisher添加显式的完成方法,强制在返回前触发修改操作,彻底规避析构顺序带来的不确定性:
struct Finisher { A & a; bool completed = false; ~Finisher() { // 兜底逻辑:若未显式完成,析构时仍执行修改 if (!completed) { a.i = 1; } } A& finish() { a.i = 1; completed = true; return a; } }; A f() { A a{0}; Finisher fr{a}; return fr.finish(); // 先触发修改再返回对象 }
这样无论编译器采用哪种执行顺序,finish()都会先将a.i设为1,再返回a,最终输出必然为1。
内容的提问来源于stack exchange,提问作者Fedor
相关产品推荐
相关产品推荐

