Висячий указатель (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]. Как и с указателем, «напечаталось правильное число» ничего не доказывает — обращение через висячую ссылку — Неопределённое поведение.