Висячий указатель (dangling pointer) — указатель, который хранит адрес уже несуществующего объекта. Сам указатель жив, объекта нет. Чтение или запись через такой указатель — Неопределённое поведение.
Зачем нужно
Указатель и объект живут независимо. Компилятор не следит за тем, чтобы объект прожил не меньше указателя на него, — это обязанность программиста. Ошибка не видна при компиляции и часто не видна при запуске: старые байты какое-то время остаются на месте, и программа «работает».
Как работает
Указатель провисает, когда область действия объекта заканчивается раньше, чем область действия указателя (см. Область действия).
С автоматической переменной — на выходе из блока:
int main() {
int* p = nullptr;
{
int x = 0;
p = &x;
} // x уничтожена, p указывает в никуда
return *p; // UB
}С динамической памятью — после delete. Обращение к
объекту после удаления называется use after free:
int* p = new int(5);
delete p;
return *p; // UB: use after freeСредство против второго случая на лекции сформулировано коротко: не обращаться
к объекту после delete.
Тонкости
Не из лекции
Оба случая ловит AddressSanitizer: сборка
g++ -g -fsanitize=addressна первом примере печатаетERROR: AddressSanitizer: stack-use-after-scope, на втором —heap-use-after-free(GCC 16.2). См. Компиляция и сборка.
Частые ошибки
- Сохранить адрес переменной из внутреннего блока или из функции и пользоваться им снаружи.
- Вызвать
deleteи продолжать пользоваться тем же указателем. - Удалить объект через один указатель, забыв, что на него смотрит второй.
- Делать вывод «UB нет» из того, что программа напечатала правильное число.
Углубление — L05
Та же проблема бывает со ссылкой — висячая ссылка (dangling reference). Классический способ получить её — вернуть из функции ссылку на локальную переменную:
int& F() {
int x = 0;
return x; // x умрёт в конце вызова
}Код с лекции перечисляет, на что вернуть ссылку или указатель можно:
- на объект в динамической памяти;
- на глобальную переменную — любой объект со статической областью действия,
включая
static-локальные (см. Область действия); - на переменную, переданную в функцию как параметр по ссылке.
Не из лекции
GCC 16.2 ловит этот случай при компиляции:
reference to local variable 'x' returned [-Wreturn-local-addr]. Как и с указателем, «напечаталось правильное число» ничего не доказывает — обращение через висячую ссылку — Неопределённое поведение.