gcc 4.x与gcc 6.x之间libstdc++的make_shared内存布局是否变化?
首先要明确一个关键事实:_GLIBCXX_USE_CXX11_ABI=0强制启用的旧ABI(C++03时代的ABI)并不是完全一成不变的。GCC在维护旧ABI的过程中,会为了修复bug、优化实现对标准库内部细节做调整——而这些调整在跨版本编译的代码中,就可能引发兼容性问题,你的例子就是典型场景。
具体崩溃原因拆解
你的例子中,foo.cxx用GCC4.8.2编译,main.cxx用GCC6.2.0编译(都指定旧ABI),问题出在std::make_shared<X>的内部实现上:
std::make_shared的核心优化是一次性分配对象实例和shared_ptr的控制块(_Sp_counted_ptr_inplace)的内存,把两者打包在同一块内存区域里。- GCC4.8.2和GCC6+的旧ABI版本中,这个打包内存块的布局(比如控制块的大小、内部成员的偏移)发生了不兼容的变化。
- 当
foo()调用make_shared时,用的是GCC4.8.2的布局逻辑来写入内存,但GCC6+编译的主程序(带ASAN)会按照自己的布局预期来检查内存访问——这就导致了ASAN检测到堆缓冲区溢出:写入的位置超出了GCC6+预期的内存范围。
为什么GCC5.4没问题?
GCC5.x是C++11 ABI切换的过渡版本,它的旧ABI实现和GCC4.8.x的兼容性做得更好,没有引入导致这种内存布局冲突的变更。而GCC6+对旧ABI的内部实现做了更多调整(比如适配新特性的回退逻辑、修复共享指针的线程安全bug),这些调整在跨版本混用旧ABI时就暴露了问题。
为什么只针对std::make_shared?
std::make_shared的内存打包逻辑是标准库的私有实现细节,不属于公开ABI的范畴。而如果改用std::shared_ptr<X>(new X()),对象和控制块是分开分配的,shared_ptr本身的接口和内存布局属于公开ABI,跨版本混用旧ABI时兼容性更好。但make_shared的内部组合方式没有强制的ABI约束,不同GCC版本可以自由调整,这就导致了跨版本崩溃。
解决方案
- 统一编译版本:所有翻译单元必须用同一版本的GCC编译,哪怕都指定旧ABI也不能跨版本混用。
- 替换
make_shared:如果必须跨版本编译,暂时改用std::shared_ptr<X>(new X())替代std::make_shared<X>(),牺牲一点内存分配效率来保证兼容性。
内容的提问来源于stack exchange,提问作者Barry
相关产品推荐
相关产品推荐

