Как бороться с утечками памяти?
- title
- Как бороться с утечками памяти?
- type
- summary
- summary
- Ответ Страуструпа в C++ FAQ: писать код без утечек, пряча выделение памяти в контейнеры и дескрипторы ресурсов, а не выискивать их постфактум
- parent
- raii
- tags
- cpp, memory-management, language-design
- sources
- c-style-and-technique-faq
- created
- 2026-05-08
- updated
- 2026-05-08
- lang
- ru
- translation_of
- stroustrup-memory-leaks
- source_updated
- 2026-05-08
- translated
- 2026-09-01
- translator
- lllm/antigravity/gemini-3.7-flash-medium
Ответ Бьёрна Страуструпа на вопрос "How do I deal with memory leaks?" в его C++ Style and Technique FAQ состоит всего из одного абзаца, и большая его часть - это ссылка: "By writing code that doesn't have any" ("Писать код, в котором их нет"). В остальной части заметки разъясняется, что это значит на практике, и формулируется каноническое краткое описание дисциплины, ставшей впоследствии raii.
Аргумент
Добросовестность не масштабируется. Если new, delete и адресная арифметика размазаны по большой программе, объём ручного учёта рано или поздно превысит возможности любого разработчика, и утечки или повисшие указатели станут неизбежностью, а не вероятностью. По утверждению Страуструпа, это никак не зависит от вашей аккуратности: дело в структуре кода, а не в программисте.
Решение - спрятать выделение и освобождение памяти внутрь типов, владеющих своими ресурсами. Канонический пример - стандартные контейнеры (std::string, std::vector): они управляют памятью для своих элементов лучше написанного вручную кода и "без чрезмерных усилий". FAQ сопоставляет небольшую программу на vector<string> (в ней нет new, delete, приведений типов и ограничений по размеру) с той же логикой, написанной без контейнеров, где отсутствие утечек потребовало бы отдельного доказательства.
Пример из 1981 года
Самая конкретная часть заметки - короткое утверждение из 1981 года: сократив число явно отслеживаемых объектов с "десятков тысяч до нескольких дюжин", Страуструп превратил неподъёмную задачу в выполнимую. Это исходный количественный аргумент в пользу того, что позже стало RAII, - не философское предпочтение, а ограничение на размер рабочего множества сущностей, за которыми человек способен уследить без ошибок.
Дескрипторы ресурсов для остального
Когда выделение памяти нельзя скрыть внутри существующего контейнера, Страуструп предлагает использовать дескриптор ресурса (resource handle). В FAQ приводится auto_ptr (давно вытесненный unique_ptr и shared_ptr, но принцип тот же) и наглядно показывается пример с потенциальной утечкой:
S* f() // who is responsible for deleting this S?
{
return new S;
}
auto_ptr<S> g() // explicitly transfer responsibility
{
return auto_ptr<S>(new S);
}
Суть не в механизме умных указателей, а в том, что владение становится частью типа. Нельзя случайно потерять результат вызова g() так, чтобы деструктор при этом не сработал. Этот принцип обобщается ("думайте о ресурсах вообще, а не только о памяти") на файловые дескрипторы, блокировки, сокеты и любые другие сущности с парными операциями захвата и освобождения.
Запасной вариант
Заметка завершается одной оговоркой: если приходится использовать код, "полученный со стороны, часть программы писали неандертальцы и т. д.", используйте детектор утечек памяти или подключите сборщик мусора (garbage collector). Это преподносится как мера устранения последствий, а не как вариант по умолчанию. Отношение сообщества C++ - что GC нужен для кода, который вы не контролируете, а не как базовая часть языка - отчётливо видно в этом единственном предложении.
Почему это актуально и сейчас
Заметка в FAQ старая (пример с auto_ptr указывает на эпоху до C++11), но сама формулировка идеи сохранилась лучше синтаксиса. В современном C++ вместо auto_ptr используются unique_ptr и shared_ptr, а в стандартной библиотеке появилось больше контейнеров, однако лежащее в основе утверждение - отсутствие утечек зависит от структуры кода, а не от личной дисциплины - это в точности то правило, которое simplified-model-of-fil-c пытается навязать в C/C++ механически через теневые возможности (shadow capabilities) и фоновый GC. Ответ Страуструпа - это общественный договор; Fil-C - принудительное исполнение на уровне среды выполнения.
Это также перекликается с no-silver-bullet: явное управление памятью - это случайная (accidental) сложность, а стандартные контейнеры - инструмент борьбы с ней. Сокращение на порядки из примера 1981 года (от тысяч к десяткам) - как раз тот прирост производительности на порядок, появление которого от отдельной технологии Брукс считал невозможным. И RAII, пожалуй, дал такой прирост - пусть и для одной конкретной категории ошибок.