c++ while 循环相当于 for 循环列表遍历 + 擦除不起作用

问题描述 投票:0回答:1

考虑以下函数

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

c++ stl stdvector erase
1个回答
1
投票

两者并不等同。

这个:

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
取消引用未定义的结束迭代器。

© www.soinside.com 2019 - 2024. All rights reserved.