Никогда (прямо или косвенно) не возвращайте указатель или ссылку на локальный объект
Причина
Чтобы избежать сбоев и повреждения данных, которые могут возникнуть при использовании такого висячего указателя.
Пример (плохой)
После возврата из функции её локальные объекты больше не существуют:
int* f()
{
int fx = 9;
return &fx; // ПЛОХО
}
void g(int* p) // выглядит достаточно безобидно
{
int gx;
cout << "*p == " << *p << '\n';
*p = 999;
cout << "gx == " << gx << '\n';
}
void h()
{
int* p = f();
int z = *p; // чтение из покинутого стекового фрейма (плохо)
g(p); // передача указателя на покинутый стековый фрейм функции (плохо)
}
На одной популярной реализации я получил следующий вывод:
*p == 999
gx == 999
Я ожидал этого, потому что вызов g() повторно использует стековое пространство, покинутое вызовом f(), поэтому *p ссылается на пространство, теперь занятое gx.
- Представьте, что бы произошло, если бы
fxиgxбыли разных типов. - Представьте, что бы произошло, если бы
fxилиgxбыли типом с инвариантом. - Представьте, что бы произошло, если бы этот висячий указатель передавался среди большего набора функций.
- Представьте, что мог бы сделать злоумышленник с этим висячим указателем.
К счастью, большинство (все?) современных компиляторов обнаруживают и предупреждают об этом простом случае.
Примечание
Это применимо также к ссылкам:
int& f()
{
int x = 7;
// ...
return x; // Плохо: возвращает ссылку на объект, который вот-вот будет уничтожен
}
Примечание
Это применимо только к не-static локальным переменным. Все static переменные (как следует из их названия) размещаются статически, поэтому указатели на них не могут стать висячими.
Пример (плохой)
Не все примеры утечки указателя на локальную переменную так очевидны:
int* glob; // глобальные переменные плохи по многим причинам
template<class T>
void steal(T x)
{
glob = x(); // ПЛОХО
}
void f()
{
int i = 99;
steal([&] { return &i; });
}
int main()
{
f();
cout << *glob << '\n';
}
Здесь мне удалось прочитать местоположение, покинутое вызовом f. Указатель, хранящийся в glob, мог бы использоваться гораздо позже и вызвать проблемы непредсказуемым образом.
Примечание
Адрес локальной переменной может быть «возвращён»/утечь через оператор return, через T& выходной параметр, как член возвращаемого объекта, как элемент возвращаемого массива и другими способами.
Примечание
Аналогичные примеры можно построить с «утечкой» указателя из внутренней области видимости во внешнюю; такие примеры обрабатываются эквивалентно утечкам указателей из функции.
Несколько иная разновидность проблемы — помещение указателей в контейнер, переживающий указуемые объекты.
Смотрите также: Другой способ получения висячих указателей — инвалидация указателей. Это можно обнаружить/предотвратить аналогичными техниками.
Контроль
- Компиляторы склонны обнаруживать возврат ссылки на локальные объекты и во многих случаях могут обнаружить возврат указателей на локальные объекты.
- Статический анализ может обнаружить многие распространённые паттерны использования указателей, обозначающих позиции (тем самым устраняя висячие указатели).