C 语言

关注公众号 jb51net

关闭
首页 > 软件编程 > C 语言 > C++ 返回值失效

C++ 返回值失效的问题解决

作者:BigTopOne

本文用通俗的场景和黄金法则,详细解析了传值返回、引用返回、指针返回等各种情况下的安全性,并介绍了返回值优化RVO,看完后,你将彻底搞懂局部变量返回的坑,避免悬空指针和内存泄漏问题

楔子

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;   // 这个指针指向的对象还活着,不悬空

一张表记住所有情况

返回什么安不安全原因
局部变量的值✅ 安全销毁前先拷贝/移动出去
局部变量的引用/指针❌ 危险函数结束就销毁,引用/指针悬空
传引用进来的参数的引用✅ 安全对象在调用者那边,函数没有权力销毁它
传值进来的参数的引用❌ 危险参数本身也是局部变量
类成员的引用/指针⚠️ 看情况只要 *this 对象还活着就安全
容器内部元素的指针(之后又改了容器)❌ 危险容器扩容/删除会让内部指针失效
new/shared_ptr 管理的对象✅ 不悬空但要小心生命周期归属(泄漏/重复释放)

记忆口诀:先问自己"这块内存归谁管、什么时候会被回收",再看你手里的引用/指针会 不会活得比它管理的内存还长。

顺带一提:RVO(返回值优化)

到此这篇关于C++ 返回值失效的问题解决的文章就介绍到这了,更多相关C++ 返回值失效内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文