Содержание
В векторе лежит число 42. Мы запоминаем ссылку на него, добавляем в конец число 99 и пробуем прочитать первое. Кажется, ответ очевиден. Но программа может вывести 42, напечатать совсем другое число или упасть. Разберёмся, в какой именно строке потерялась связь с первым элементом.
Как воспроизвести ошибку
3 ноября вышел LLVM 23.1.5. Воспользуемся знакомым инструментом из комплекта Clang — AddressSanitizer, сокращённо ASan. Он добавляет в программу проверки и при запуске показывает, где она обращается к уже освобождённой памяти. Для опыта подойдёт и более ранний Clang с поддержкой ASan.
Сохраните пример в main.cpp. Строка с resize нужна только для эксперимента. Она заполняет все свободные места в векторе, так что для следующего числа точно понадобится новый участок памяти.
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores{42};
scores.resize(scores.capacity());
int& first = scores[0];
scores.push_back(99);
std::cout << first << '\n';
}
int& first даёт другое имя первому числу в памяти. Отдельная копия числа при этом не создаётся. Представьте, что вы записали адрес ячейки, где лежит 42. Заменить адрес в этой записи вектор за вас не сможет.
Что меняется при добавлении числа
У вектора есть размер и запас места. size() показывает, сколько элементов лежит в нём сейчас. capacity() показывает, сколько поместится до следующего переезда. Например, при размере 3 и ёмкости 4 четвёртый элемент займёт свободное место. Для пятого придётся искать участок побольше.
В нашем примере свободных мест уже нет. При вызове push_back вектор выделяет новый участок, переносит туда элементы и освобождает старый. Число 42 по-прежнему первое. Только старая ссылка first ведёт туда, где оно лежало раньше. Ошибка возникает в последней строке, когда программа пытается прочитать число по этой ссылке.
Как увидеть ошибку в терминале
Откройте терминал в папке с примером. Если у вас GCC с поддержкой AddressSanitizer, соберите и запустите программу командами ниже. Здесь g++ — компилятор C++ из GCC. Если он установлен под именем g++-16, подставьте это имя в первую команду. Для Clang с установленной библиотекой ASan подойдёт clang++ с теми же флагами.
g++ -std=c++17 -O0 -g -fsanitize=address -fno-omit-frame-pointer main.cpp -o demo
./demo
На GCC 16.2 с включённой проверкой мы получили heap-use-after-free и READ of size 4. Первая запись означает, что программа обратилась к уже освобождённой памяти. Вторая — что она пыталась прочитать четыре байта. В нашем примере это чтение first в последней строке. Ниже, под словами freed by, можно проследить освобождение памяти до вызова push_back. Если в отчёте видны номера строк, начните с ближайшей строки своего файла.
Два способа исправить код
Если нужно запомнить счёт на тот момент, уберите амперсанд. Объявление int first = scores[0]; сохраняет самостоятельную копию числа 42. Позже сам счёт может измениться, а снимок останется прежним.
Если нужен актуальный счёт первого игрока, запомните его позицию. После добавления в конец элементы сохраняют порядок, и первый игрок по-прежнему находится под номером ноль. Вот полный исправленный пример.
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores{42};
scores.resize(scores.capacity());
const auto first_index = 0;
scores.push_back(99);
std::cout << scores[first_index] << '\n';
}
После исправления мы заново собрали программу с той же проверкой. Она вывела 42 и завершилась без сообщения ASan. Теперь scores[first_index] обращается к текущей памяти вектора. Если же вы ещё и удаляете игроков или сортируете таблицу, старый индекс может указывать на другого человека. Тогда пригодится постоянный идентификатор игрока.
Проверьте себя
Предположим, вектор уже содержит один элемент, а его ёмкость равна четырём. Вы берёте ссылку на первый элемент и по очереди дописываете числа в конец. После какого добавления придётся заново получить ссылку?
После четвёртого добавления. Второй, третий и четвёртый элементы ещё помещаются, а пятый вызывает перенос. Если число элементов известно заранее, можно вызвать reserve до получения ссылки. Если предел заранее неизвестен, проще запоминать позицию и получать элемент заново в момент чтения.
Источники
- LLVM. Выпуск LLVM 23.1.5 [Электронный ресурс]. — URL: https://llvm.org/ (дата обращения: 07.11.2026).
- Clang. AddressSanitizer [Электронный ресурс]. — URL: https://clang.llvm.org/docs/AddressSanitizer.html (дата обращения: 07.11.2026).
- Рабочий проект стандарта C++. Вставка элементов в vector [Электронный ресурс]. — URL: https://eel.is/c++draft/vector.modifiers (дата обращения: 07.11.2026).
- Рабочий проект стандарта C++. Ёмкость vector [Электронный ресурс]. — URL: https://eel.is/c++draft/vector.capacity (дата обращения: 07.11.2026).
- GCC. Параметры инструментальной проверки [Электронный ресурс]. — URL: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html (дата обращения: 07.11.2026).



