#c #vector #dereference
#c #вектор #разыменование
Вопрос:
Почему valgrind возвращает ошибку для этого фрагмента кода?
#include <iostream>
#include <vector>
int main()
{
std::vector<int> vec;
int *ptr;
for (int i = 0; i < 1000; i )
{
vec.push_back(i);
if (i == 100)
{
ptr = amp;vec[i];
}
}
std::cout << ptr << "n"; // Print address of -> Ok
std::cout << *ptr << "n"; // Print content of -> Ok but with a valgrind error
}
Скомпилировано с: g -Wall -Wpedantic -O0 -o demo demo.cpp
Ошибка valgrind:
==3982== Invalid read of size 4
==3982== at 0x1093A7: main (in /home/david/demo)
==3982== Address 0x4dae1e0 is 400 bytes inside a block of size 512 free'd
==3982== at 0x483E1CF: operator delete(void*, unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3982== by 0x109EB4: __gnu_cxx::new_allocator<int>::deallocate(int*, unsigned long) (in /home/david/demo)
==3982== by 0x109B5B: std::allocator_traits<std::allocator<int> >::deallocate(std::allocator<int>amp;, int*, unsigned long) (in /home/david/demo)
==3982== by 0x10970F: std::_Vector_base<int, std::allocator<int> >::_M_deallocate(int*, unsigned long) (in /home/david/demo)
==3982== by 0x109A3B: void std::vector<int, std::allocator<int> >::_M_realloc_insert<int constamp;>(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int constamp;) (in /home/david/demo)
==3982== by 0x10964F: std::vector<int, std::allocator<int> >::push_back(int constamp;) (in /home/david/demo)
==3982== by 0x109354: main (in /home/david/demo)
==3982== Block was alloc'd at
==3982== at 0x483CE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==3982== by 0x10A0FF: __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (in /home/david/demo)
==3982== by 0x109F73: std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>amp;, unsigned long) (in /home/david/demo)
==3982== by 0x109DAD: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (in /home/david/demo)
==3982== by 0x1098BC: void std::vector<int, std::allocator<int> >::_M_realloc_insert<int constamp;>(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int constamp;) (in /home/david/demo)
==3982== by 0x10964F: std::vector<int, std::allocator<int> >::push_back(int constamp;) (in /home/david/demo)
==3982== by 0x109354: main (in /home/david/demo)
==3982==
Это заставляет меня сомневаться, имеет ли разыменование адреса, возвращаемого вектором, неопределенное поведение, является ли это законным кодом?
Комментарии:
1. Из-за проблемы перераспределения, когда и указатели, и итераторы на элементы могут слишком легко стать недействительными, вместо этого лучше сохранить индексы .
2. Другое возможное решение — установить размер вектора раньше и просто использовать индексы. Или в вашем случае это можно было бы решить с
std::iota
помощью вместо явного цикла.3. Однако @Someprogrammerdude OP не запрашивает обходной путь, они довольно четко спрашивают, является ли показанный код законным или нет.
4. @cigien Ну, я не пишу ответ, только комментарии.
5. @cigien верно, но любое предложение приветствуется. Вопрос возникает из-за того, что библиотеки, которые «перераспределяют память», вообще не проясняют этот момент, например , g_array (из glib) использует аналогичную концепцию (using
realloc
), и я думаю, что было бы очень уместно уточнить это, поскольку это может привести к ошибкам. Я вижу, что это тот же случай,std::vector
но может быть, в этом случае это хорошо документировано, есть ли какая-либо ссылка в стандарте?
Ответ №1:
Valgrind сообщает, что это «недопустимое чтение». Во время push_back
операции std::vector
при необходимости будет перераспределена память и скопированы все данные в новое местоположение.
Если это так, возможно, что ваш ptr
указывает на память, которая больше не выделяется вектору vec
.
Так что да, это может быть UB так, как вы его используете.
Сначала лучше обратиться к reserve
памяти, если вы уже знаете количество элементов, которые собираетесь вставить, а затем просто вставьте элементы в нужное место.
Комментарии:
1. На самом деле, я думаю, что это может быть только UB. Реализация могла бы выделить достаточно памяти, чтобы перераспределение не выполнялось после получения адреса элемента.
2. @cigien: Да, вы правы. Я обновлю
3. другой способ — получить не адрес элемента для будущего использования, а его индекс в векторе
4. Спасибо за редактирование ответа, чтобы прояснить немного о UB. Я внес несколько незначительных правок, чтобы немного прояснить ответ, надеюсь, все в порядке.
Ответ №2:
Что касается последней части вопроса о разыменовании указателей на векторные индексы.
Пожалуйста, поправьте меня, если я ошибаюсь, но я понимаю, что элементы в векторе являются просто указателями на фактические данные. Массив векторных элементов должен находиться в непрерывной памяти, чтобы их можно было индексировать, но данные могут быть где угодно.
Расширение вектора происходит быстро, потому что вам нужно только переместить индексы в новый больший блок непрерывной памяти, а не в фактические данные. При первом развертывании вектора с помощью .push_back массив индексов переместится в памяти, поэтому любые указатели, которые у вас были на них, станут недействительными после первого раза, зависящего от его реализации.
Комментарии:
1. элементы в векторе — это просто указатели на фактические данные . Можете ли вы подробнее рассказать об этом? Насколько я понимаю, вектор — это указатель на непрерывную область памяти, что-то вроде растущего массива… данные могут быть где угодно … если вы имеете в виду, что они представляют собой массив указателей, где каждый элемент находится во фрагментированной памяти, я думаю, вы ошибаетесь.
2. Ну, я действительно имею в виду то, что говорю. Если вы разыменуете имя вектора, он выдаст вам текущий адрес первого элемента в массиве указателей на ваши данные.
3. Я думаю, что одной из основных особенностей вектора является то, что не имеет значения, насколько велик ваш тип данных, потому что при расширении вектора перемещаются только указатели на данные, поэтому они быстро перемещаются. Строки в стиле C очень похожи на векторы, но в этом случае было бы глупо хранить указатель на один символ, поэтому данные фактически хранятся в индексе.
4. Я не согласен (вектор не хранит указатели), но спасибо за ответ.
5. Ах да, я не имел в виду указатели на стиль C / C . Индексы вектора являются дескрипторами или «векторами» для элементов данных, но они по-прежнему указывают на данные, они не являются фактическими данными.