考虑以下函数
void removeOdd(vector<int>& v)
{
for(vector<int>::iterator it=v.begin(); it!=v.end(); )
{
if((*it)%2) it = v.erase(it);
else it++;
}
}
当链接到以下测试脚本时
#include <vector>
#include <algorithm>
#include <iostream>
#include <cassert>
using namespace std;
void test()
{
int a[9] = { 5, 2, 8, 9, 6, 7, 3, 4, 1 };
vector<int> x(a, a+9); // construct x from the array
assert(x.size() == 9 && x.front() == 5 && x.back() == 1);
removeOdd(x);
assert(x.size() == 4);
sort(x.begin(), x.end());
int expect[4] = { 2, 4, 6, 8 };
for (int k = 0; k < 4; k++)
assert(x[k] == expect[k]);
}
int main()
{
test();
cout << "Passed" << endl;
}
并使用 WSL g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 和 -fsanitize=address 标志进行编译,工作得很好。
编译命令:
g++ -fsanitize=address test.cpp -o test
test
的输出:Passed
但是,以下 while 循环类似
removeOdd
,
void removeOdd_new(vector<int>& v)
{
for(vector<int>::iterator it=v.begin(); it!=v.end(); it++)
{
while ((*it)%2)
it = v.erase(it);
}
}
当使用相同的编译器在相同的选项下编译时,即
g++ -fsanitize=address test_new.cpp -o test_new
,运行时抛出以下错误
==207338==ERROR: AddressSanitizer: negative-size-param: (size=-4)
#0 0x7f26ba2d5f97 in __interceptor_memmove ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:810
#1 0x5577715186c7 in int* std::__copy_move<true, true, std::random_access_iterator_tag>::__copy_m<int>(int const*, int const*, int*) (/mnt/c/Users/somebody/Downloads/test_new+0x86c7)
#2 0x557771517a2b in int* std::__copy_move_a2<true, int*, int*>(int*, int*, int*) (/mnt/c/Users/somebody/Downloads/test_new+0x7a2b)
#3 0x5577715166e0 in int* std::__copy_move_a1<true, int*, int*>(int*, int*, int*) (/mnt/c/Users/somebody/Downloads/test_new+0x66e0)
#4 0x5577715157fb in __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > std::__copy_move_a<true, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > >(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >) (/mnt/c/Users/somebody/Downloads/test_new+0x57fb)
#5 0x557771514e37 in __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > std::move<__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > > >(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, __gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >) (/mnt/c/Users/somebody/Downloads/test_new+0x4e37)
#6 0x5577715144bd in std::vector<int, std::allocator<int> >::_M_erase(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >) (/mnt/c/Users/somebody/Downloads/test_new+0x44bd)
#7 0x557771513792 in std::vector<int, std::allocator<int> >::erase(__gnu_cxx::__normal_iterator<int const*, std::vector<int, std::allocator<int> > >) (/mnt/c/Users/somebody/Downloads/test_new+0x3792)
#8 0x5577715125ec in removeOdd_new(std::vector<int, std::allocator<int> >&) (/mnt/c/Users/somebody/Downloads/test_new+0x25ec)
#9 0x557771512b91 in test() (/mnt/c/Users/somebody/Downloads/test_new+0x2b91)
#10 0x557771512f44 in main (/mnt/c/Users/somebody/Downloads/test_new+0x2f44)
#11 0x7f26b9d69d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#12 0x7f26b9d69e3f in __libc_start_main_impl ../csu/libc-start.c:392
#13 0x5577715123c4 in _start (/mnt/c/Users/somebody/Downloads/test_new+0x23c4)
0x604000000024 is located 20 bytes inside of 36-byte region [0x604000000010,0x604000000034)
allocated by thread T0 here:
#0 0x7f26ba3521e7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99
#1 0x5577715168f7 in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (/mnt/c/Users/somebody/Downloads/test_new+0x68f7)
#2 0x557771515b79 in std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (/mnt/c/Users/somebody/Downloads/test_new+0x5b79)
#3 0x5577715150fb in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (/mnt/c/Users/somebody/Downloads/test_new+0x50fb)
#4 0x557771514824 in void std::vector<int, std::allocator<int> >::_M_range_initialize<int*>(int*, int*, std::forward_iterator_tag) (/mnt/c/Users/somebody/Downloads/test_new+0x4824)
#5 0x557771513950 in std::vector<int, std::allocator<int> >::vector<int*, void>(int*, int*, std::allocator<int> const&) (/mnt/c/Users/somebody/Downloads/test_new+0x3950)
#6 0x557771512a79 in test() (/mnt/c/Users/somebody/Downloads/test_new+0x2a79)
#7 0x557771512f44 in main (/mnt/c/Users/somebody/Downloads/test_new+0x2f44)
#8 0x7f26b9d69d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
SUMMARY: AddressSanitizer: negative-size-param ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:810 in __interceptor_memmove
==207338==ABORTING
我无法理解 while 循环版本抛出地址错误的原因,因为 while 循环在逻辑上与 for 循环没有什么不同。
我还明白,
std::vector.erase()
的返回值是“(a)n迭代器,指向函数调用删除的最后一个元素后面的元素的新位置。如果操作删除了最后一个元素,则这是容器结束在序列中。”本文档取自 https://cplusplus.com/reference/vector/vector/erase/。
因此,while循环中的变量
it
应该相应更新(但会抛出错误)。如果对 cpp 内部结构有深入了解的人可以帮助解释编译器错误消息,我也将不胜感激。谢谢!
test.cpp 和 test_new.cpp 链接:https://hackmd.io/54Mm2B1pStSPTWOkXJ4QtQ
两者并不等同。
这个:
void removeOdd(vector<int>& v) { for(vector<int>::iterator it=v.begin(); it!=v.end(); ) { if((*it)%2) it = v.erase(it); else it++; } }
将迭代向量中的所有元素。它会递增
it
直到 it==v.end()
,然后函数返回。
另一方面,这里
void removeOdd_new(vector<int>& v) { for(vector<int>::iterator it=v.begin(); it!=v.end(); it++) { while ((*it)%2) it = v.erase(it); } }
一旦
it
到达最后一个元素,如果该元素是odd
,它将被删除,it
变成v.end()
并且*it
取消引用未定义的结束迭代器。