Разыменование “A non-dereferenceable iterator”а

104
18 декабря 2020, 02:50

Простой пример:

#include <iostream>
#ifdef _ITERATOR_DEBUG_LEVEL
#undef _ITERATOR_DEBUG_LEVEL
#define _ITERATOR_DEBUG_LEVEL 0
#endif
#include <vector>
using namespace std;
int main()
{
    vector<int> vec{ 1,2,3 };
    auto it = vec.begin();
    cout << *it << endl;
    vec.shrink_to_fit();
    vec.push_back(42);
    cout << *it << endl; // Итератор все еще указывает на старую позицию,
                         // и мы точно не знаем, что с тех пор произошло.
                         // это не самая корректная запись, мы знаем об этом
                         // но для чистоты эксперимента, она здесь!
}

Добавим в вектор новое число. Поскольку он недостаточно велик [vec.shrink_to_fit();], чтобы свободно принять новое число, он автоматически увеличится в размере. Это достигается за счет выделения более крупного фрагмента памяти, перемещения всех существующих элементов в новый фрагмент и удаления старого.

ВОПРОС: Можно ли уверенно сказать, что результат работы последней строки в коде -- "это неопределенное поведение (1.3.24 [defns.undefined] undefined behavior)"?

P.S. Прошу внимательно прочитать ВОПРОС:, перед тем как ответить, и прошу по возможности ссылаться на источники вроде Черновик стандарта C++ и/или на авторитетную литературу. Спасибо!

P.S. Для чистоты эксперимента я написал

#define _ITERATOR_DEBUG_LEVEL 0

в своем коде и увидел следующий результат:
1
-572662307

Answer 1

Да, это неопределённое поведение.

Я, конечно, не напрямую стандарт процитирую, но, думаю, cppreference достаточно авторитетный источник.

По шагам.

  1. shrink_to_fit

цитата:

a) "It is a non-binding request to reduce capacity() to size(). It depends on the implementation whether the request is fulfilled. "

b) "If reallocation occurs, all iterators, including the past the end iterator, and all references to the elements are invalidated. If no reallocation takes place, no iterators or references are invalidated."

Как видим, уже на этом этапе сохранённый итератор begin может инвалидироваться (если запрос на реаллокацию прошёл, а прошёл он или нет - зависит от реализации, стандарт не обязывает его выполнить, может так случиться, что запрос не пройдёт, и тогда итератор останется жив, но это "случайность")

  1. push_back

"If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated."

Если в ранее выполненном запросе на уменьшение памяти запрос прошёл (что, как мы выяснили, уже убило итератор), то произойдёт реаллокация и все сохранённые итераторы окажутся инвалидированы.

Может так случиться, что в вашем конкретном компиляторе shrink_to_fit ничего не делает, это стандарт не запрещает (вроде бы), тогда ваш итератор останется цел, но это как раз проявление неопределённого поведения, в зависимости от компилятора конкретная картина может отличаться. В целом в вашем примере нельзя надеяться на конкретное событие, это и означает, что здесь UB.

READ ALSO
unsigned char* и char* при работе со строками

unsigned char* и char* при работе со строками

Могут ли возникнуть проблемы с работой со строками если использовать не char* /const char*, а unsigned char*/const unsigned char*? Если все правильно, к примеру, в UTF-8,...

118
500 (Internal Server Error)

500 (Internal Server Error)

Имеется форма с двумя выпадающими списками и одной кнопкойПо нажатию на кнопку отправляются данные в виде json, на основе этих данных выполняется...

149
encodeURIComponent синтаксис

encodeURIComponent синтаксис

Как правильно кусок кода засунуть в encodeURIComponent при отправке ajax запроса?

129