Когда мы определяем символьный массив как 'символьное имя [10]', указывает это, что массив 'имя' может содержать строку длины десять символов. Но в программе, показанной ниже массива, имя может содержать больше чем десять символов. Как это возможно?
//print the name of a person.
char name[10];
scanf("%s",name);
printf("%s",name);
Здесь, если я ввожу имя даже длины, больше, чем десять символов, нет никакой ошибки периода выполнения, и программа печатает все символы, которые я ввел. Существует завершение программы, если я ввожу имя двадцати или больше символов.
Примечание: Я запускаю программу на Ubuntu9.04 с помощью gcc компилятора.
scanf позволяет задать максимальную ширину, как в
scanf("%9s", name);
Это позволит прочитать до 9 символов и добавить завершающий символ NUL, итого 10 символов.
Что произойдет, если вы не ограничите количество символов, которые может прочитать scanf? Ну, тогда ваша строка окажется перезаписанной. В данном случае, я полагаю, ваш буфер находится в стеке, поэтому вы перезаписываете что-то в стеке. В стеке хранятся локальные переменные, адреса возврата (к функции, которая вызвала эту функцию) и аргументы функции. Теперь злоумышленник может заполнить этот буфер произвольным кодом и перезаписать адрес возврата адресом этого кода (существует множество вариантов этой атаки). Злонамеренный пользователь может выполнить произвольный код через эту программу.
Потому что scanf не знает, какой длины массив. Переменная "name" имеет тип не "массив", а "указатель" (или "адрес"). Это говорит: начните писать здесь и продолжайте писать, пока не закончите. Возможно, вам повезет, и в вашем стеке окажутся другие не столь важные вещи, которые будут перезаписаны, но в конце концов scanf будет писать, писать и писать, и перезапишет что-то фатальное, и вы получите Segmentation Fault. Вот почему вы всегда должны передавать размер массивов.
Это похоже на то, как если дать слепому человеку карандаш и сказать: "Начинай писать здесь", при этом он не видит, где конец бумаги. В конце концов они напишут на столе и что-нибудь повредят. (Примечание: это не оскорбление слепых, это просто метафора.)
В приведенном выше случае я настоятельно рекомендую использовать fgets() для получения определенной суммы из stdin, а затем sscanf() для извлечения любой информации из этой строки и помещения ее в отдельные переменные по мере необходимости. Scanf() и fscanf() - это зло, я никогда не находил для них применения, которое не могли бы более безопасно решить fgets()+sscanf().
char line[1024]; /* arbitrary size */
if( fgets( line, 1024, stdin ) != NULL )
{
fprintf( stdout, "Got line: %s", line );
}
Или для вещей за пределами строк:
# cat foo.c
#include <stdio.h>
int main( int argc, char **argv )
{
int i;
char line[1024];
while( fgets( line, 1024, stdin ) != NULL )
{
if( sscanf( line, "%d", &i ) == 1 )
{ /* 1 is the number of variables filled successfully */
fprintf( stdout, "you typed a number: %d\n", i );
}
}
}
# gcc foo.c -o foo
# ./foo
bar
2
you typed a number: 2
33
you typed a number: 33
<CTRL-D>
В массиве размером 10 символов для представления строки на языке C вы действительно можете использовать только 9 символов и завершающий символ нулем. Если вы используете более 9 символов (+1 завершение), то у вас будет неопределенное поведение.
Вы просто перезаписываете память, чего быть не должно. То, что происходит, будь то segfault или работа, как вы ожидаете, так же хорошо, как и случайное.
Добро пожаловать в мир C...
scanf
(как использовано в примере программы Mohit
) не обрабатывает ограничение размера буфера назначения; В C нет проверок на длину массива. Это позволит вам переполнить массив.
В вашем случае после массива есть память, доступная для записи, поэтому вы не упадете, если переполните массив на небольшую величину (хотя кто знает, что вы испортите).
Попробуйте этот код и посмотрите, что произойдет, если вы введете более 10 символов.
char name[10];
char name2[10];
scanf("%s",name);
printf("%s",name);
printf("%s",name2);
Также массив имен может содержать 9 символов, десятым должен быть завершающий нулевой ноль '\0'
Как это возможно?
Массив выделяется на стеке. За ним может быть пустое пространство или данные, которые имеют меньшее значение, чем национальная безопасность (например, регистры callee-saves, которые фактически не используются в вызывающей программе). В конце концов, если введенное вами имя достаточно длинное, вы перезапишете что-то важное. В том числе, в некоторых компиляторах, обратный адрес!
Запуск программы под valgrind мгновенно обнаружит ошибку превышения.
Вы эксплуатируете неопределенное поведение, поэтому может произойти все, что угодно - программа может дать сбой, или продолжить работу, или начать делать что-то странное.
Когда вы говорите char c[10], вы выделяете 10 байт для этой переменной. Однако, ваша программа может "владеть" и последующими байтами, поэтому вы можете не получить segfault. Но вы столкнетесь с таким количеством других проблем, что пожалеете, что не получили segfault.
Ваш код вызывает неопределенное поведение.
Никогда не используйте scanf ()
для чтения строки, вместо этого используйте fgets ()
.
scanf ()
и gets ()
имеют точно такую же проблему с переполнением памяти. Вы можете легко прочитать больше символов, чем может вместить ваш char []
.