std::string对象被释放后仍然访问std::string::c_str()返回的指针(访问已经释放掉的内存)的一些总结

发布时间 2023-04-20 20:48:56作者: 程序员没有头发

一个值得注意的事情

今天在调试程序,发现一个严重又很难排查的问题!

有关如何正确使用函数的”返回值“。

先看一下简单代码:

#include <iostream>
using namespace std;

const char *test1()
{
    std::string str = "hello";
    const char * ptr = str.c_str();
    return ptr;
}

const char *test2()
{
    std::string str = "world";
    const char * ptr = str.c_str();
    return ptr;
}

int main()
{
    const char *str1 = test2();
    const char *str2 = test1();

    std::cout << "str1: " << str1 << std::endl;
    std::cout << "str2: " << str2 << std::endl;
    return 0;
}

我使用QT的编译器,得出结果:

咋乱码了呢,不应该str1: hello str2: world 这样子的结果吗?而且每一次编译运行的乱码都不一样...

分析原因

以上程序中,函数test1和test2返回一个char*指针,这个指针指向的是函数内部临时新建的std::string对象,但是当函数运行到最后一个花括号}时,里面所有申请的临时对象、变量的内存空间都会被自动释放掉!!!而在主函数使用函数返回的指针char *时,这时候访问的是一个已经释放掉的内存空间,所以打印的是乱码。

但是!我有个疑问,就算内存释放掉了,那块空间的地址之前所存储的内容应该还是会存在的呀!(意思就是说:我就算访问已经释放掉内存,但是内存的内容我没有手动清空,不应该还会存在吗?这时候去访问,不应该还会访问到原来的数据吗?)

深入了解

其实上面这么想也有道理,遇事不决,问问GPT!



我问的太啰嗦了哈哈哈,综上所述,也就是说,当一块内存被手动/自动释放后:

如果系统还没对内存进行回收(清空内存内容;将内存块标记为空闲),这时候去访问是有可能访问到上一次的数据的,但是数据会有乱码、或者被新申请的内存数据覆盖掉,所以是不安全的!

结合大佬的文章《操作系统资源回收问题——delete或者free释放的内存会立即回收到操作系统吗?

内存(memory)是在堆(heap)上分配的。

当进程 (process)请求内存时是向堆(堆管理器)请求内存,而堆又向操作系统(OS)申请内存。由于这种操作代价比较大,操作系统一般是分配给一块(chunk)内存给堆,以减少内存操作(还是说系统调用)次数。

因此进程调用delete或者free释放资源后,这些资源归还给了这个程序所申请到的堆,而堆不一定会将资源归还给操作系统(取决于操作系统类型、内存块的大小等因素)。

这部分没有归还的资源在当前进程再次(使用new或malloc)申请内存时可以被重用,因此这样可以避免频繁与操作系统进行“内存交互”。这些释放的资源在进程结束后随着整个堆一起归还给OS。

解决方案

我就是要使用函数返回的值,那要怎么做?

直接将返回值赋值std::string对象,让它重新构造一次算了。

#include <iostream>
using namespace std;

string test1()
{
    std::string str = "hello";
    return str;
}

string test2()
{
    std::string str = "world";
    return str;
}

int main()
{
    //将函数返回值,重新赋值string对象,让其再构造一次,相当于拷贝数据
    string str1 = test1();
    string str2 = test2();

    std::cout << "str1: " << str1.c_str() << std::endl;
    std::cout << "str2: " << str2.c_str() << std::endl;
    return 0;
}

输出结果:

正确!