CentOS7迁移RHEL8后C++代码返回无效字符串问题排查
我们公司有一套在CentOS 7上稳定运行多年的框架,迁移至RHEL 8后部分单元测试失败,其中一个问题是std::runtime_error的what()返回垃圾值。我写了一段极简C++代码复现该问题:这段代码在CentOS 7运行正常,但在RHEL 8中返回无效值。
复现代码
#include <sstream> #include <iostream> const char * getString() { std::ostringstream oss; oss << "some stuff to return" << std::endl; std::cout << "value to return: " << oss.str().c_str() << std::endl; return oss.str().c_str(); } int main(int argc, char ** argv) { std::string value = getString(); std::cout << "value returned: " << value << std::endl; return 0; }
CentOS 7运行输出
[user@localhost ~]$ ./a.out value to return: some stuff to return value returned: some stuff to return
RHEL 8运行输出
[user@localhost badoverload]$ ./a.out value to return: some stuff to return value returned: `
提问
请问是否是值超出作用域导致内存不可用,只是CentOS7中内存恰好未被修改,还是存在其他原因?
回答
没错,核心原因就是超出作用域导致内存被释放,CentOS 7里只是运气好内存没被覆盖而已。
看你的代码:getString()里返回的是oss.str().c_str(),这里oss.str()会生成一个临时的std::string对象,c_str()返回的是这个临时对象内部的字符数组指针。等这条语句执行完,这个临时std::string就会被销毁,它管理的内存也会被释放。
回到main函数里用std::string value = getString();时,你拿到的是一个指向已经被释放内存的指针,这时候去构造新的std::string属于未定义行为——也就是说C++标准没规定这种情况会发生什么,完全取决于编译器、操作系统、内存管理机制的具体实现。
CentOS 7用的是旧版GCC(比如4.8.x),可能在临时对象销毁后,内存暂时没被其他操作覆盖,所以指针还能读出原来的内容;而RHEL 8用的是新版GCC(比如8.x),可能内存被立即清零或者被其他数据覆盖了,所以读出来就是垃圾值。
解决方法很简单,直接返回std::string而不是const char*:
std::string getString() { std::ostringstream oss; oss << "some stuff to return" << std::endl; std::cout << "value to return: " << oss.str() << std::endl; return oss.str(); }
这样std::string会正确地把数据复制一份返回,不会出现内存悬空的问题。
内容的提问来源于stack exchange,提问作者mybodycravesbutterygoodness

