Массив символов в C - PullRequest
       1

Массив символов в C

5 голосов
/ 11 июля 2010

Когда мы определяем массив символов как 'char name [10]', это указывает, что массив 'name' может содержать строку длиной десять символов. Но в показанной ниже программе имя массива может содержать более десяти символов. Как это возможно?

//print the name of a person.  
char name[10];  
scanf("%s",name);  
printf("%s",name);  

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

Примечание: я запускаю программу на Ubuntu9.04 с использованием компилятора gcc.

Ответы [ 9 ]

7 голосов
/ 11 июля 2010

Потому что scanf не знает, какой длины массив.Переменная «имя» относится не к типу «массив», а к типу «указатель» (или «адрес»).Здесь написано, начните писать здесь и продолжайте писать, пока не закончите.Возможно, вам повезет, и у вас в стеке есть другие не критичные вещи, которые будут перезаписаны, но в конечном итоге scanf напишет, напишет и перезапишет что-то фатальное, и вы получите ошибку сегментации.Вот почему вы всегда должны указывать размер массивов.

Это все равно, что давать слепому человеку карандаш и говорить «начинайте писать здесь», чтобы они не могли видеть, где находится конец статьи.Они в конечном итоге напишут на столе и повредят что-нибудь.(Примечание: это не стук в слепую, это всего лишь метафора.)

В приведенном выше случае я настоятельно рекомендую использовать 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>
4 голосов
/ 11 июля 2010

С массивом размером 10 символов для представления строки в C На самом деле вы можете использовать только 9 символов и завершенный нулем символ. Если вы используете более 9 символов (+1 завершение), у вас будет неопределенное поведение.

Вы просто перезаписываете память, которой не должны быть. Что происходит, будь то segfault или работа, как вы ожидаете, случайна.

3 голосов
/ 11 июля 2010

scanf допускает указатель максимальной ширины, как в

scanf("%9s", name);

Это прочитает до 9 символов и добавит завершающий символ NUL, всего 10 символов.

Что произойдет, если вы не ограничите количество символов, которые может прочитать scanf? Ну, тогда ваша строка переписывает что-то еще. В этом случае, я думаю, ваш буфер находится в стеке, поэтому вы что-то перезаписываете в стеке. В стеке хранятся локальные переменные, адреса возврата (функции, вызвавшей эту функцию) и аргументы функции. Теперь злоумышленник может заполнить этот буфер произвольным кодом и перезаписать обратный адрес адресом этого кода (существует много вариантов этой атаки). Злоумышленник может выполнить произвольный код через эту программу.

2 голосов
/ 11 июля 2010

Добро пожаловать в мир C ...

  • C не выполняет проверку границ массива;
  • имя массива - не что иное, как указатель на первый элемент массива;
  • scanf (как в примере программы Mohit) не обрабатывает ограничение размера буфера назначения;
  • с неправильным значением указателя вы можете записать в любом месте памяти, и вы должны ожидать непредсказуемого поведения, ошибки сегментации, если вам повезет.
1 голос
/ 11 июля 2010

Как это возможно?

Массив размещается в стеке. После него могут быть пустые места или данные, которые имеют меньшее значение, чем национальная безопасность (например, регистры сохранения вызываемого абонента, которые фактически не используются в вызывающем абоненте). В конце концов, если имя, которое вы вводите, достаточно длинное, вы перезаписываете что-то важное. В том числе, при некоторых компиляторах, обратный адрес!

Запуск программы под valgrind мгновенно обнаружит ошибку переполнения.

1 голос
/ 11 июля 2010

C не проверяет длину массива.Это позволит вам переполнить массив.

В вашем случае после массива оказывается доступная для записи память, поэтому вы не аварийно завершите работу, если переполнитесь небольшим количеством (хотя кто знает, что вы портите).

Попробуйте этот коди посмотрим, что произойдет, если вы введете более 10 символов.

char name[10];
char name2[10];  
scanf("%s",name);  
printf("%s",name);  
printf("%s",name2); 

Также массив имен может содержать 9 символов, 10-й должен быть завершающим нулем '\ 0'

0 голосов
/ 11 июля 2010

Когда вы говорите char c [10], вы выделяете 10 байтов для этой переменной.Однако ваша программа может также «владеть» последующими байтами, поэтому вы можете не получить сегментную ошибку.Но вы столкнетесь с множеством других проблем, которые вам хотелось бы, чтобы у вас был сбой.

0 голосов
/ 11 июля 2010

Вы используете неопределенное поведение, поэтому может произойти все, что угодно - программа может аварийно завершить работу, продолжить работу или начать делать что-то странное.

0 голосов
/ 11 июля 2010

Ваш код вызывает неопределенное поведение. Никогда используйте scanf() для чтения строки, используйте fgets() вместо.

scanf() и gets() имеют точно такую ​​же проблему с переполнением памяти. Вы можете легко прочитать больше символов, чем может вместить ваш char[].

...