C++中vector<bitset>迭代器触发AddressSanitizer堆释放后使用错误
vector<bitset<26>>迭代器引发heap-use-after-free问题解析
问题背景
使用vector<bitset<26>>迭代器时,编写的极简程序被AddressSanitizer检测到**heap-use-after-free(堆释放后使用)**警告。未启用Sanitizer时程序可正常执行,将endIter设为next(test.begin())也能正常运行,需分析问题根源。
代码示例
#include <vector> #include <bitset> using namespace std; int main(int argc, char const *argv[]) { vector<bitset<26>> test(2); auto endIter { test.end() }; for (auto iter { test.begin() }; iter != endIter; ++iter) test.push_back(*iter); return 0; }
AddressSanitizer警告信息
==8229==ERROR: AddressSanitizer: heap-use-after-free on address 0x000107200718 at pc 0x000105378d14 bp 0x00016b14b080 sp 0x00016b14a838 READ of size 8 at 0x000107200718 thread T0 #0 0x105378d10 in __asan_memcpy+0x1a4 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x3cd10) #1 0x104cba394 in void std::__1::allocator<std::__1::bitset<26ul>>::construct<std::__1::bitset<26ul>, std::__1::bitset<26ul> const&>(std::__1::bitset<26ul>*, std::__1::bitset<26ul> const&) allocator.h:154 #2 0x104cba33c in void std::__1::allocator_traits<std::__1::allocator<std::__1::bitset<26ul>>>::construct<std::__1::bitset<26ul>, std::__1::bitset<26ul> const&, void>(std::__1::allocator<std::__1::bitset<26ul>>&, std::__1::bitset<26ul>*, std::__1::bitset<26ul> const&) allocator_traits.h:290 #3 0x104cb9dac in void std::__1::vector<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>>::__construct_one_at_end<std::__1::bitset<26ul> const&>(std::__1::bitset<26ul> const&) vector:948 #4 0x104cb74d4 in std::__1::vector<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>>::push_back(std::__1::bitset<26ul> const&) vector:1660 #5 0x104cb6d54 in main test.cpp:10 #6 0x104f5d088 in start+0x204 (dyld:arm64e+0x5088) 0x000107200718 is located 8 bytes inside of 16-byte region [0x000107200710,0x000107200720) freed by thread T0 here: #0 0x10538a330 in wrap__ZdlPv+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x4e330) #1 0x104cb988c in void std::__1::__libcpp_operator_delete<void*>(void*) new:245 #2 0x104cb983c in void std::__1::__do_deallocate_handle_size<>(void*, unsigned long) new:269 #3 0x104cb97e0 in std::__1::__libcpp_deallocate(void*, unsigned long, unsigned long) new:285 #4 0x104cb9770 in std::__1::allocator<std::__1::bitset<26ul>>::deallocate(std::__1::bitset<26ul>*, unsigned long) allocator.h:117 #5 0x104cb95e4 in std::__1::allocator_traits<std::__1::allocator<std::__1::bitset<26ul>>>::deallocate(std::__1::allocator<std::__1::bitset<26ul>>&, std::__1::bitset<26ul>*, unsigned long) allocator_traits.h:282 #6 0x104cbb570 in std::__1::__split_buffer<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>&>::~__split_buffer() __split_buffer:347 #7 0x104cba8d0 in std::__1::__split_buffer<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>&>::~__split_buffer() __split_buffer:344 #8 0x104cba190 in void std::__1::vector<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>>::__push_back_slow_path<std::__1::bitset<26ul> const&>(std::__1::bitset<26ul> const&) vector:1651 #9 0x104cb74e4 in std::__1::vector<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>>::push_back(std::__1::bitset<26ul> const&) vector:1663 #10 0x104cb6d54 in main test.cpp:10 #11 0x104f5d088 in start+0x204 (dyld:arm64e+0x5088) previously allocated by thread T0 here: #0 0x105389ef0 in wrap__Znwm+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x4def0) #1 0x104cb8b7c in void* std::__1::__libcpp_operator_new<unsigned long>(unsigned long) new:235 #2 0x104cb8a48 in std::__1::__libcpp_allocate(unsigned long, unsigned long) new:261 #3 0x104cb898c in std::__1::allocator<std::__1::bitset<26ul>>::allocate(unsigned long) allocator.h:108 #4 0x104cb84c0 in std::__1::allocator_traits<std::__1::allocator<std::__1::bitset<26ul>>>::allocate(std::__1::allocator<std::__1::bitset<26ul>>&, unsigned long) allocator_traits.h:262 #5 0x104cb7a64 in std::__1::vector<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>>::__vallocate(unsigned long) vector:1015 #6 0x104cb76b8 in std::__1::vector<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>>::vector(unsigned long) vector:1148 #7 0x104cb6f70 in std::__1::vector<std::__1::bitset<26ul>, std::__1::allocator<std::__1::bitset<26ul>>>::vector(unsigned long) vector:1142 #8 0x104cb6c7c in main test.cpp:7 #9 0x104f5d088 in start+0x204 (dyld:arm64e+0x5088) SUMMARY: AddressSanitizer: heap-use-after-free (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x3cd10) in __asan_memcpy+0x1a4 Shadow bytes around the buggy address: 0x007020e60090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x007020e600a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x007020e600b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x007020e600c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x007020e600d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x007020e600e0: fa fa fd[fd]fa fa 00 04 fa fa 00 06 fa fa 00 06 0x007020e600f0: fa fa 00 00 fa fa fd fd fa fa fa fa fa fa fa fa 0x007020e60100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x007020e60110: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x007020e60120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x007020e60130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==8229==ABORTING
编译环境
Apple clang version 14.0.0 (clang-1400.0.29.102) Target: arm64-apple-darwin21.6.0
编译参数:-fsanitize=address
问题原因分析
这不是AddressSanitizer的问题,核心是忽略了vector迭代器的失效规则:
vector的push_back操作如果触发扩容(当前容量不足以容纳新元素),会释放原内存块,重新分配更大的内存空间并拷贝原元素。- 内存重新分配后,所有指向原内存的迭代器(包括
begin()、end()以及提前保存的endIter)都会失效,成为野指针。
你的代码中,初始vector有2个元素,第一次push_back刚好在容量范围内,第二次push_back触发扩容,原内存被释放,iter和endIter指向已释放的内存区域,后续循环使用这些失效迭代器就会触发heap-use-after-free。
当endIter设为next(test.begin())时,循环仅执行1次,不会触发扩容,迭代器未失效,因此程序正常。未启用Sanitizer时,操作系统可能未回收已释放的内存,程序侥幸运行,但这属于未定义行为,随时可能崩溃或产生错误结果。
修复方案
方案1:预先分配足够容量,避免扩容
vector<bitset<26>> test(2); test.reserve(4); // 预先分配能容纳4个元素的容量 auto endIter = test.end(); for (auto iter = test.begin(); iter != endIter; ++iter) test.push_back(*iter);
方案2:基于原大小循环,避免迭代器失效
vector<bitset<26>> test(2); size_t originalSize = test.size(); // 保存初始元素数量 for (size_t i = 0; i < originalSize; ++i) test.push_back(test[i]);
内容的提问来源于stack exchange,提问作者R-penguins
相关产品推荐
相关产品推荐

