C++程序中string的const引用超出作用域后仍有效是什么原因?
问题现象说明
存在如下代码逻辑:
#include <iostream> #include <string> #include <memory> using namespace std; struct Def { const string& s; Def(const string& v):s(v) { } }; int main() { unique_ptr<Def> d = nullptr; { string cst = "Hello"; d.reset(new Def(cst)); std::cout <<&d->s <<"="<< &cst<<"\n"; } std::cout << "strVal : " << d->s<< "\n"; return 0; }
按照C++基础语法规则,块作用域内创建的string cst会在块结束时销毁,存储了其const引用的unique_ptr<Def> d的成员s应该变成悬垂引用,但运行时却正确打印了"Hello"字符串,且打印的两个地址完全一致,没有发生拷贝。
原因解答
- 首先必须明确:该代码属于未定义行为,"运行正常"只是未定义行为的偶发表现,不代表代码逻辑合法,在不同编译参数、编译器版本、运行环境下完全可能出现乱码、程序崩溃等异常结果。
- 块作用域结束时,
cst作为自动变量确实会被销毁,其占用的栈内存会被编译器标记为可复用,但是运行时不会主动擦除这块内存中存储的原有内容。你的示例代码逻辑非常简单,在块结束到打印d->s之间没有任何操作占用这块被释放的栈内存,所以原有的"Hello"字符串内容暂时保留在了原地,读取时自然就拿到了旧值。 - 该现象也和
std::string的短字符串优化(SSO)特性相关:大部分主流标准库的std::string实现都会把短于特定长度(通常为15/23字节)的字符串直接存储在std::string对象本身的栈空间中,不需要额外申请堆内存存储。所以cst销毁时只要对应的栈内存未被覆盖,就能读到完整的字符串内容,如果换成超过SSO阈值的长字符串,对应的堆内存会在cst销毁时被回收,读取出现异常的概率会高很多。 - 你可以自行修改代码验证:在块结束后、打印
d->s之前新增局部变量、或者调用任意存在局部变量的函数覆盖这块栈内存,再打印的结果大概率就是乱码了。 - 额外说明:C++中const引用延长生命周期的规则仅适用于绑定临时对象的场景,你这里引用绑定的是已经存在的左值
cst,不触发生命周期延长规则,所以引用确实是悬垂状态。
内容的提问来源于stack exchange,提问作者CY5
相关产品推荐
相关产品推荐

