Проклятие неинициализированных переменных

Позволять программистам использовать неинициализированные переменные — большая ошибка со стороны разработчиков языка. Такую оплошность легко совершить и тяжело отследить. Особенно при выполнении программы на разных платформах. И необходимости в этой особенности нет — переменная всегда должна иметь определенное значение.

Проблема

Локальные переменные, переменные-поля и т.п. являют собой неинициализированные переменные, т.е. в них будет записано ровно то, что было записано в отведенной под них памяти при объявлении. В C++ существуют различные правила для работы с неинициализированными переменными, инициализированными переменными и нуль-инициализированными переменными. Очень путанный механизм.

Учитывая частоту, с которой в коде появляются неинициализированные переменные, можно подумать, что отследить подобные ситуации в коде легко. Совсем нет. Да, конечно же, большая часть автоматически инициализируется нулем, если не указано иное. Однако так происходит не всегда. Например, в результате выполнения этого кода:

В результате выполнения этого кода у автора статьи всегда печатается False и 0. Ошибок, связанных с утечкой памяти, можно избежать, используя valgrind.

Но основная проблема заключается в том, что программы даже в таком случае продолжают работать, что снижает вероятность обнаружения ошибки. Обычно ее можно отследить, только лишь запустив тестирование на другой платформе.

Почему ноль?

Почему программа автоматически инициализирует переменную нулем? На самом деле, это делает не программа, а операционная система, которая просто не позволит приложению проникнуть в неинициализированную область памяти, такова природа этого защитного механизма. Если программа A отработала свое, и результаты ее работы остались где-то в памяти, то программа B не должна обнаружить их во время своей работы, а потому ядро самостоятельно очищает память. В частности, Linux прописывает в освободившуюся память нули.

Естественно, нет никакого правила, чем заполнять память. Вероятно, OpenBSD делает это как-то иначе. Иначе чистит память и ArchLinux, запущенный в VirtualBox. Этим может заниматься не только операционная система — то же может проделать и другая программа, например. И если вдруг в область памяти, которую использует приложение, попадут какие-нибудь значения, изменить их сможет уже только сама эта программа.

Любопытно, что это стало одной из причин появления Heartbleed бага.

Решение

Язык просто не должен позволять использовать неинициализированные переменные. Необязательно нужно указывать конкретное значение — значение просто обязательно должно быть. Например, значением по умолчанию может стать все тот же ноль. Вне зависимости от того, как и в какой области видимости я создал данную переменную.

В то же время, могут быть сделаны определенные исключения в пользу оптимизации. Например, при работе с памятью на низком уровне. Впрочем, оптимизатор обычно обнаруживает неиспользуемые переменные и может их принудительно не инициализировать. Если нам нужен блок неинициализированной памяти, у нас должна быть возможно выделить ее самостоятельно. В этом случае программист четко отдает себе отчет в производимых им действиях, а потому не должен попасть в ловушку.

И чтобы полностью отследить все моменты с инициализацией памяти, язык также должен иметь специальное значение noinit, которое будет показывать, что данная переменная не нуждается в инициализации.

Автор статьи уверен, что уже в ближайшем стандарте C++ должны быть реализованы эти изменения. Превращение неинициализированных ранее переменных в инициализированные не повлияет на корректность выполнения ни одной программы. Такое нововведение будет полностью backwards compatible, и серьезно улучшит популярный язык.

Перевод статьи «The uninitialized variable anathema: non-deterministic C++»