楔子
- make_number 方法中 amf0value a; a 是局部变量,return a 以后,a 离开了它的作用域,不会失效吗
static amf0value make_number(double v) {
amf0value a;
a.type = amf0_number;
a.number = v;
return a;
}
黄金法则
看这个"东西"在函数返回之后,是否还"活着"。活着 → 安全;已经被销毁了却还在引用它 → 悬空(dangling),未定义行为。
逐个场景过一遍
1. 传值返回(return by value)—— 永远安全
amf0value make_number(double v) {
amf0value a; // 局部变量
return a; // 返回的是"值",销毁a之前会先把内容搬给调用者
}
调用者拿到的是独立的新数据,跟原来的 a 无关。a 死不死不重要。这是 amf0.h 里 make_number/make_bool/make_string 这些工厂方法的写法,天然安全。
为什么安全:函数返回类型是 amf0value(值),不是 amf0value&(引用)也不是 amf0value*(指针)。return a; 这一步,是在 a 被销毁之前,先把 a 的内容拷贝 (或者移动)一份给调用者。等这个"搬运"动作完成之后,函数才真正结束,局部变量 a 才被销毁——调用者手里的东西跟 a 已经没关系了。
类比 java:java 里 amf0value a = new amf0value();,a 本质是一个指向堆上对象 的引用,方法返回 a 返回的也是这个引用(地址),指向的还是堆上同一个对象,对象生死 由 gc 管,跟方法作用域无关。c++ 不一样:amf0value a; 默认是栈上的真实对象,作用域 结束是真的会销毁的。正因为这样,c++ "返回值"这个动作,语义上就是"把值搬到外面", 而不是"返回一个指向它的地址"。
2. 返回局部变量的引用/指针 —— 永远危险 ⚠️
amf0value& make_number_bad(double v) {
amf0value a;
return a; // 函数结束 a 被销毁,这个引用悬空
}
amf0value* make_number_bad2(double v) {
amf0value a;
return &a; // 同样悬空
}
记住"局部变量 + 引用/指针返回"这个组合就行——这是最经典也是最容易踩的坑。
3. 返回参数的引用 ——看参数本身是什么✅✅✅✅
// 安全:外面传进来的对象,函数结束后它在调用者那边继续活着
const std::string& first_nonempty(const std::string& a, const std::string& b) {
return a.empty() ? b : a; // a、b 都是调用者的对象,没被这个函数销毁
}
// 危险:value 是"传值"进来的参数,本质也是这个函数的局部变量
const std::string& make_bad(std::string value) {
return value; // value 是这个函数自己的局部拷贝,函数结束就销毁了
}
4. 返回类成员变量的引用/指针 —— 看这个对象(*this)活多久
struct amf0value {
std::vector<std::pair<std::string, std::shared_ptr<amf0value>>> obj;
const amf0value* get(const std::string& key) const {
for (auto& kv : obj) if (kv.first == key) return kv.second.get();
return nullptr;
}
};
这个 get() 项目里就有——只要拿到这个指针的时候,那个 amf0value 对象本身还没被 销毁,就是安全的。但如果把这个对象整个销毁了,之前 get() 拿到的指针立刻变悬空, 这不是函数的问题,是调用方要自己保证"对象还活着才能用它返回的指针"。
5. 更隐蔽的近亲问题:容器重新分配导致的失效
这个跟"局部变量"无关,但是同一类"东西已经不在了却还在用"的问题,值得一起记住:
const amf0value* p = obj_value.get("app"); // 假设此刻拿到了一个指针
obj_value.set("newkey", ...); // vector push_back,可能触发扩容重新分配内存
// p 现在可能已经悬空了!因为 vector 扩容时会把所有元素搬到新内存,
// 旧内存被释放,p 指向的地址不再有效
这叫迭代器/引用失效(iterator/reference invalidation),跟局部变量销毁是两码事, 但本质都是"你手里的指针/引用指向的内存已经不归你想的那个对象用了"。规则:只要还 可能对容器做增删操作,就不要长期持有指向它内部元素的指针/引用。
6.new出来的对象、shared_ptr—— 不会悬空,但换了一种风险
amf0value* p = new amf0value(); // 堆上分配,函数结束不会自动销毁 return p; // 这个指针指向的对象还活着,不悬空
- 这种不会"失效",但换成了谁负责 delete 它的问题——忘记删就是内存泄漏,删两次 就是 double free ⚠️ ⚠️ ⚠️ 。
- amf0value::obj 用 std::shared_ptr 而不是裸指针,就是为了让这 块内存的生命周期自动管理,不用手动跟踪该不该 delete,这也是现代 c++ 处理这类问题的 标准做法。
一张表记住所有情况
| 返回什么 | 安不安全 | 原因 |
|---|---|---|
| 局部变量的值 | ✅ 安全 | 销毁前先拷贝/移动出去 |
| 局部变量的引用/指针 | ❌ 危险 | 函数结束就销毁,引用/指针悬空 |
| 传引用进来的参数的引用 | ✅ 安全 | 对象在调用者那边,函数没有权力销毁它 |
| 传值进来的参数的引用 | ❌ 危险 | 参数本身也是局部变量 |
| 类成员的引用/指针 | ⚠️ 看情况 | 只要 *this 对象还活着就安全 |
| 容器内部元素的指针(之后又改了容器) | ❌ 危险 | 容器扩容/删除会让内部指针失效 |
| new/shared_ptr 管理的对象 | ✅ 不悬空 | 但要小心生命周期归属(泄漏/重复释放) |
记忆口诀:先问自己"这块内存归谁管、什么时候会被回收",再看你手里的引用/指针会 不会活得比它管理的内存还长。
顺带一提:rvo(返回值优化)
- 现代 c++ 编译器在"传值返回局部变量"这种场景下,通常会做 rvo(return value optimization)——直接把局部变量构造在调用者接收返回值的那块内存上,连拷贝这一步都 省了。
- 就算编译器没做这个优化,c++11 之后也会自动优先用"移动"而不是"拷贝"(amf0value 里的 std::string、std::vector 这些成员移动起来很便宜)。 这是性能层面的细节,不影 响"安不安全"这个问题——安全性上,只要是传值返回,就没有失效风险。
到此这篇关于c++ 返回值失效的问题解决的文章就介绍到这了,更多相关c++ 返回值失效内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论