В вычислительной технике неинициализированная переменная — это переменная , которая объявлена, но не установлена в определенное известное значение перед использованием. Она будет иметь некоторое значение, но непредсказуемое. Таким образом, это ошибка программирования и распространенный источник ошибок в программном обеспечении.
Обычное предположение, которое делают начинающие программисты, заключается в том, что все переменные устанавливаются в известное значение, например, ноль, когда они объявляются. Хотя это верно для многих языков, это верно не для всех из них, и поэтому существует вероятность ошибки. Такие языки, как C, используют стековое пространство для переменных, а набор переменных, выделенных для подпрограммы, известен как стековый кадр . Хотя компьютер выделяет соответствующее количество пространства для стекового кадра, он обычно делает это просто путем корректировки значения указателя стека и не устанавливает саму память в какое-либо новое состояние (обычно из соображений эффективности). Следовательно, любое содержимое этой памяти в данный момент будет отображаться как начальные значения переменных, которые занимают эти адреса.
Вот простой пример на языке C:
void count ( void ) { int k , i ; for ( i = 0 ; i < 10 ; i ++ ) { k = k + 1 ; } printf ( "%d" , k ); }
Конечное значение k
не определено. Ответ, что оно должно быть 10, предполагает, что оно начиналось с нуля, что может быть правдой, а может и нет. Обратите внимание, что в этом примере переменная i
инициализируется нулем первым предложением оператора for
.
Другой пример может быть при работе со структурами . В приведенном ниже фрагменте кода у нас есть , struct student
который содержит некоторые переменные, описывающие информацию о студенте. Функция register_student
допускает утечку содержимого памяти, поскольку ей не удается полностью инициализировать элементы struct student new_student
. Если мы посмотрим внимательнее, то в начале инициализируются , age
и semester
. student_number
Но инициализация элементов first_name
и last_name
неверна. Это происходит потому, что если длина first_name
и last_name
массивов символов меньше 16 байт, во время strcpy
, [1] мы не можем полностью инициализировать все 16 байт памяти, зарезервированной для каждого из этих элементов. Следовательно, после memcpy()
преобразования полученной структуры в output
, [2] мы допускаем утечку некоторой стековой памяти в вызывающую функцию.
struct student { unsigned int age ; unsigned int semester ; char first_name [ 16 ]; char last_name [ 16 ]; unsigned int student_number ; }; int register_student ( struct student * output , int age , char * first_name , char * last_name ) { // Если какой-либо из этих указателей равен Null, происходит сбой. if ( ! output || ! first_name || ! last_name ) { printf ( "Error! \n " ); return -1 ; } // Мы гарантируем, что длина строк меньше 16 байт (включая нулевой байт) // чтобы избежать переполнений if ( strlen ( first_name ) > 15 || strlen ( last_name ) > 15 ) { printf ( "first_name и last_name не могут быть длиннее 16 символов! \n " ); return -1 ; } // Инициализация членов структуры student new_student ; new_student . age = age ; new_student . semester = 1 ; new_student . student_number = get_new_student_number (); strcpy ( new_student . first_name , first_name ); strcpy ( new_student . last_name , last_name ); //копирование результата в вывод memcpy ( output , & new_student , sizeof ( struct student )); return 0 ; }
В любом случае, даже если переменная неявно инициализируется значением по умолчанию , например 0, это обычно не является правильным значением. Инициализированный не означает правильный, если значение является значением по умолчанию. (Однако инициализация по умолчанию значением 0 является правильной практикой для указателей и массивов указателей, поскольку она делает их недействительными до того, как они будут фактически инициализированы своим правильным значением.) В языке C переменные со статической продолжительностью хранения, которые не инициализируются явно, инициализируются нулем (или null для указателей). [3]
Неинициализированные переменные не только являются частой причиной ошибок, но этот тип ошибок особенно серьезен, поскольку он может быть невоспроизводимым: например, переменная может оставаться неинициализированной только в некоторой ветви программы. В некоторых случаях программы с неинициализированными переменными могут даже проходить тесты программного обеспечения .
Неинициализированные переменные являются мощными ошибками, поскольку они могут быть использованы для утечки произвольной памяти или для достижения произвольной перезаписи памяти или для получения выполнения кода, в зависимости от случая. При эксплуатации программного обеспечения, которое использует рандомизацию адресного пространства (ASLR), часто требуется знать базовый адрес программного обеспечения в памяти. Эксплуатация неинициализированной переменной таким образом, чтобы заставить программное обеспечение утечь указатель из своего адресного пространства, может быть использована для обхода ASLR.
Неинициализированные переменные являются особой проблемой в таких языках, как ассемблер, C и C++ , которые были разработаны для системного программирования . Разработка этих языков включала в себя философию дизайна, в которой конфликты между производительностью и безопасностью обычно решались в пользу производительности. На программиста возлагалось бремя осознания опасных проблем, таких как неинициализированные переменные.
В других языках переменные часто инициализируются известными значениями при создании. Примеры включают:
NULL
(отличным от None
) и вызывает исключение UnboundLocalError
при доступе к такой переменной перед (повторной) инициализацией допустимым значением.Даже в языках, где разрешены неинициализированные переменные, многие компиляторы будут пытаться определить использование неинициализированных переменных и сообщать о них как об ошибках времени компиляции . Некоторые языки помогают в этой задаче, предлагая конструкции для обработки инициализации переменных; например, в C# есть особый вид параметров вызова по ссылке для подпрограмм (указанных как вместо обычного ), утверждающих, что переменная может быть неинициализирована при входе, но будет инициализирована впоследствии.out
ref