Неинициализированная переменная
В вычислениях неинициализированная переменная — это переменная , которая объявлена, но не имеет определенного известного значения перед ее использованием. Это будет иметь некоторую ценность, но не предсказуемую. По существу, это ошибка программирования и распространенный источник ошибок в программном обеспечении.
Пример языка C
[ редактировать ]Начинающие программисты обычно полагают, что всем переменным при объявлении присваивается известное значение, например ноль. Хотя это верно для многих языков, но не для всех, поэтому существует вероятность ошибки. Такие языки, как 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)
{
// If any of these pointers are Null, we fail.
if (!output || !first_name || !last_name)
{
printf("Error!\n");
return -1;
}
// We make sure the length of the strings are less than 16 bytes (including the null-byte)
// in order to avoid overflows
if (strlen(first_name) > 15 || strlen(last_name) > 15) {
printf("first_name and last_name cannot be longer than 16 characters!\n");
return -1;
}
// Initializing the members
struct 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);
//copying the result to output
memcpy(output, &new_student, sizeof(struct student));
return 0;
}
В любом случае, даже если переменная неявно инициализируется значением по умолчанию, например 0, это обычно не правильное значение. Инициализировано не означает правильно, если значение установлено по умолчанию. (Однако инициализация по умолчанию значением 0 является правильной практикой для указателей и массивов указателей, поскольку она делает их недействительными до того, как они фактически будут инициализированы правильным значением.) В C переменные со статической продолжительностью хранения, которые не инициализируются явно, инициализируются как ноль (или ноль для указателей). [ 3 ]
Неинициализированные переменные не только являются частой причиной ошибок, но и ошибки такого рода особенно серьезны, поскольку они могут быть невоспроизводимыми: например, переменная может оставаться неинициализированной только в какой-то ветке программы. В некоторых случаях программы с неинициализированными переменными могут даже пройти программные тесты .
Воздействие
[ редактировать ]Неинициализированные переменные являются серьезными ошибками, поскольку их можно использовать для утечки произвольной памяти, перезаписи произвольной памяти или обеспечения выполнения кода, в зависимости от случая. При использовании программного обеспечения, которое использует рандомизацию расположения адресного пространства (ASLR), часто требуется знать базовый адрес программного обеспечения в памяти. Использование неинициализированной переменной для того, чтобы заставить программное обеспечение утечь указатель из ее адресного пространства , можно использовать для обхода ASLR.
Использование в языках
[ редактировать ]Неинициализированные переменные представляют собой особую проблему в таких языках, как ассемблер C и C++ , которые были разработаны для системного программирования . Разработка этих языков включала в себя философию проектирования, согласно которой конфликты между производительностью и безопасностью обычно разрешались в пользу производительности. Программисту было поручено осознавать такие опасные проблемы, как неинициализированные переменные.
В других языках переменные при создании часто инициализируются известными значениями. Примеры включают в себя:
- VHDL инициализирует все стандартные переменные специальным значением «U». Он используется при моделировании для отладки, чтобы дать пользователю знать, когда неважные начальные значения через многозначную логику влияют на выходные данные.
- В Java нет неинициализированных переменных. Поля классов и объектов, не имеющих явного инициализатора, и элементы массивов автоматически инициализируются значением по умолчанию для их типа (false для логических значений, 0 для всех числовых типов, null для всех ссылочных типов). [ 4 ] Локальным переменным в Java необходимо обязательно назначить их перед обращением к ним, иначе это будет ошибка компиляции.
- Python инициализирует локальные переменные
NULL
(в отличие отNone
) и поднимаетUnboundLocalError
когда к такой переменной осуществляется доступ до (повторной) инициализации допустимым значением. - D инициализирует все переменные, если это явно не указано программистом.
Даже в языках, где разрешены неинициализированные переменные, многие компиляторы будут пытаться идентифицировать использование неинициализированных переменных и сообщать об этом как времени компиляции об ошибках . Некоторые языки помогают решить эту задачу, предлагая конструкции для обработки инициализации переменных; например, в C# есть особый вариант параметров подпрограмм, вызываемых по ссылке (задаваемых как out
вместо обычного ref
), утверждая, что переменную можно не инициализировать при вводе, но она будет инициализирована впоследствии.
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ стркпи
- ^ мемкпи()
- ^ «ISO/IEC 9899:TC3 (текущий стандарт C)» (PDF) . 07.09.2007. п. 126 . Проверено 26 сентября 2008 г. Раздел 6.7.8, пункт 10.
- ^ «Спецификация языка Java: 4.12.5 Начальные значения переменных» . Сан Микросистемс . Проверено 18 октября 2008 г.