使用数组时析构函数出现Segmentation Error问题求助
问题现象
清理vector时出现无法解释的段错误,崩溃发生在析构函数阶段而非数组访问行,但移除数组访问代码(倒数第三行)就能消除崩溃。以下是从大型代码库提取的最小复现代码,已确认崩溃发生在析构函数而非数组写入操作时:
#include <array> #include <cstddef> #include <iostream> #include <memory> #include <vector> class Deletable { public: virtual ~Deletable() = default; }; template <typename V> class DeletableHandle : public Deletable { public: virtual V *get() = 0; virtual const V *get() const = 0; }; template <typename V> class DeletableHolder final : public DeletableHandle<V> { public: V *get() override { return &value_; } const V *get() const override { return &value_; } private: V value_; }; class InferenceContext2 { public: template <size_t LI> auto *LayerScratchSpace() { using Area = std::array<int, 4>; while (buffers_.size() <= LI) { buffers_.emplace_back(nullptr); } if (buffers_[LI] == nullptr) { buffers_[LI] = std::make_unique<DeletableHolder<Area>>(); } return reinterpret_cast<Area *>(buffers_[LI].get()); } private: std::vector<std::unique_ptr<Deletable>> buffers_; }; int main() { InferenceContext2 ctx; auto area = ctx.LayerScratchSpace<0>(); std::cout << area->size() << " " << (reinterpret_cast<intptr_t>(&area) % 128) << " " << alignof(std::remove_pointer_t<decltype(area)>) << "\n"; // Commenting out the line below removes the crash (*area)[0] = 1; return 0; }
ASAN报错信息(大型项目中)
AddressSanitizer:DEADLYSIGNAL =================================================================== ==12==ERROR: AddressSanitizer: SEGV on unknown address 0x55f800000009 (pc 0x55f85eadc1c1 bp 0x7ffd295108b0 sp 0x7ffd29510890 T0) ==12==The signal is caused by a READ memory access. #0 0x55f85eadc1c1 in std::default_delete<uchen::memory::Deletable>::operator()(uchen::memory::Deletable*) const /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:85:2 #1 0x55f85eadc0fb in std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>::~unique_ptr() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/unique_ptr.h:361:4 #2 0x55f85eadc064 in void std::destroy_at<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:88:15 #3 0x55f85eadd214 in void std::_Destroy<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:149:7 #4 0x55f85eadd1e6 in void std::_Destroy_aux<false>::__destroy<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:163:6 #5 0x55f85eadd1ac in void std::_Destroy<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_construct.h:195:7 #6 0x55f85eadd0d0 in void std::_Destroy<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>(std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>*, std::allocator<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>&) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:848:7 #7 0x55f85eadd09e in std::vector<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>, std::allocator<std::unique_ptr<uchen::memory::Deletable, std::default_delete<uchen::memory::Deletable>>>>::~vector() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_vector.h:680:2 #8 0x55f85ead8d24 in uchen::(anonymous namespace)::InferenceContext2::~InferenceContext2() /proc/self/cwd/test/arena.test.cc:71:7
复现环境
- 编译参数:
--std=c++20 - 编译器版本:
eugene-aurora:~/code$ g++ --version g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. eugene-aurora:~/code$ clang++ --version Ubuntu clang version 15.0.7 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin
问题分析
崩溃的核心原因是错误的类型转换导致内存破坏:
在LayerScratchSpace函数中,直接将DeletableHolder<Area>*通过reinterpret_cast强制转换为Area*返回。但DeletableHolder<Area>的内存布局是:基类Deletable的虚表指针 + 成员value_(std::array<int,4>)。把DeletableHolder对象的指针当成Area指针使用,会导致访问的是基类虚表指针区域而非真正的value_成员。
执行(*area)[0] = 1;时,实际修改了DeletableHolder对象的虚表指针内存,后续析构时,unique_ptr尝试调用虚析构函数时,访问被篡改的虚表指针,触发段错误。注释掉该行后,虚表指针未被破坏,析构就能正常进行。
解决方案
修复类型转换逻辑,正确获取value_的地址:
修改LayerScratchSpace函数,先将buffers_[LI].get()转换为DeletableHandle<Area>*,再调用get()方法获取真正的Area*,避免直接使用reinterpret_cast:
template <size_t LI> auto *LayerScratchSpace() { using Area = std::array<int, 4>; while (buffers_.size() <= LI) { buffers_.emplace_back(nullptr); } if (buffers_[LI] == nullptr) { buffers_[LI] = std::make_unique<DeletableHolder<Area>>(); } // 先转换为正确的派生类指针,再调用get()获取Area对象地址 auto handle = static_cast<DeletableHandle<Area>*>(buffers_[LI].get()); return handle->get(); }
这样就能保证返回的是DeletableHolder中value_的正确地址,写入操作不会破坏虚表指针,析构时也能正常执行。
内容的提问来源于stack exchange,提问作者Eugene

