std::string对象被释放后仍然访问std::string::c_str()返回的指针(访问已经释放掉的内存)的一些总结
一个值得注意的事情
最近在调试项目,发现一个意想不到的问题!程序会偶发性的产生coredump...
先看一下简单化代码:
#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;
}
输出结果:
正确!