You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:45:32