С++ цикл while эквивалент обхода списка циклов + стирание не работает

Рассмотрим следующую функцию

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/.

Таким образом, переменная it в цикле while должна соответствующим образом обновиться (но выдается ошибка). Я также был бы признателен, если бы кто-нибудь с хорошим пониманием внутреннего устройства cpp помог интерпретировать сообщения об ошибках компилятора. Спасибо!

Ссылка на test.cpp и test_new.cpp: https://hackmd.io/54Mm2B1pStSPTWOkXJ4QtQ

от: почему ты вот так смешиваешь c-массивы и векторы? Либо вы используете древний компилятор, либо можете просто написать vector<int> x{ 5, 2, 8, 9, 6, 7, 3, 4, 1 };

463035818_is_not_an_ai 22.03.2024 21:06

К вашему сведению -- void removeOdd(vector<int>& v) { v.erase(std::remove_if (v.begin(), v.end(), [](int n) { return n%2; }), v.end()); }

PaulMcKenzie 22.03.2024 21:12

@PaulMcKenzie А конкретно для векторов теперь есть std::erase().

sweenish 22.03.2024 21:28

Рекомендация: прочтите «Отладка резиновой утки».

user4581301 22.03.2024 21:36

Эти две функции не эквивалентны.

Loki Astari 22.03.2024 21:53

Assert потенциально невозможен

Swift - Friday Pie 23.03.2024 03:46

Эти две функции не эквивалентны. Функция removeOdd_new() будет увеличивать it после вызова erase() (т. е. всякий раз, когда элемент удаляется). Исходный removeOdd() не увеличивается it после вызова erase().

Peter 23.03.2024 07:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
8
84
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 разыменует конечный итератор, который не определен.

Once it reaches the last element, if that element is odd it will be erased, it becomes v.end() затем выполняется it++, пропуская итератор на один конец, в результате чего сравнение it != v.end() не работает должным образом, и вы повторно входите в цикл и происходит сбой при попытке отменить ссылку на итератор.
Loki Astari 22.03.2024 21:53

@MartinYork цикл while имеет неопределенное поведение. После этого все ставки сняты.

463035818_is_not_an_ai 22.03.2024 22:27

Другие вопросы по теме