Чтение строки с помощью scanf


147

Я немного запутался в чем-то. У меня сложилось впечатление, что правильный способ чтения C-строки scanf()шел по линии

(не берите в голову возможное переполнение буфера, это просто простой пример)

char string[256];
scanf( "%s" , string );

Тем не менее, следующее, кажется, тоже работает,

scanf( "%s" , &string );

Это только мой компилятор (gcc), чистая удача или что-то еще?


Во втором случае на самом деле возможного переполнения буфера нет, так как вы вообще не используете этот буфер. Либо так, либо вы можете сказать, что любая строка длиной более 3 символов переполнит ваш «буфер».
TED

Я имел в виду первый пример. Также другие уже указали, что здесь происходит.
abeln

Ага. Попробовал, и Гарет прав. Weird.
TED

Так как этот вопрос возвращается поиском «как прочитать строку с помощью scanf», возможно, было бы целесообразно указать, что этот вопрос касается способов передачи указателя на буфер scanf, и вопрос, и принятый ответ сосредоточены на это, и опустить критически важные ограничения для максимальной длины ввода, которые должны использоваться в реальном коде (но помимо этого вопроса).
Арку

Ответы:


141

Массив "разлагается" на указатель на свой первый элемент, поэтому scanf("%s", string)эквивалентен scanf("%s", &string[0]). С другой стороны, scanf("%s", &string)передает указатель на char[256], но он указывает на то же место.

Затем scanf, при обработке хвоста своего списка аргументов, попытается вытащить a char *. Это правильно, когда вы прошли stringили &string[0], но когда вы прошли, &stringвы зависите от того, что не гарантирует языковой стандарт, а именно от того, что указатели &stringи &string[0]- указатели на объекты различных типов и размеров, которые начать с того же места - представлены одинаково.

Я не верю, что когда-либо сталкивался с системой, в которой это не работает, и на практике вы, вероятно, в безопасности. Тем не менее, это неправильно, и это может дать сбой на некоторых платформах. (Гипотетический пример: реализация «отладки», которая включает информацию о типе с каждым указателем. Я думаю, что реализация C на Symbolics «Lisp Machines» сделала что-то вроде этого.)


5
+1. Это тривиально , чтобы проверить , что результаты распада массива в &stringработающий так же , как string(а в результате случайного повреждения памяти, как и другие ответы неправильно утверждают):printf("%x\n%x\n", string, &string);
Josh Kelley

@ Джош, Келли, интересно, я бы тогда понял, что это не так, если указатель на строку выделен через malloc ()?
abeln

4
@abeln: Верно. Для строки, выделенной через malloc (), stringуказатель и &stringадрес этого указателя, поэтому они НЕ взаимозаменяемы. Как объясняет Гарет, даже в случае распада массива, stringи &stringтехнически они не являются одними и теми же типами (даже если они взаимозаменяемы для большинства архитектур), и gcc выдаст предупреждение для вашего второго примера, если вы включите -Wall.
Джош Келли

@ Джош Келли: спасибо, я рад, что смог прояснить это.
abeln

1
@JCooper str, & str [0] представляют одно и то же (начало массива), и, хотя необязательно, & str также является одним и тем же адресом памяти. Теперь strPtr указывает на str, поэтому адрес памяти, хранящийся внутри strPtr, такой же, как str, и это 12fe60. Наконец, & strPtr - это адрес переменной strPtr, это не значения, хранящиеся в strptr, а фактический адрес памяти strPtr. Поскольку переменная strptr отличается от всех остальных, она также имеет другой адрес памяти, в данном случае 12fe54
Хуан Беса

-9

Я думаю, что это ниже, является точным, и это может помочь. Не стесняйтесь исправлять это, если вы обнаружите какие-либо ошибки. Я новичок в C.

char str[]  
  1. массив значений типа char с собственным адресом в памяти
  2. массив значений типа char, со своим собственным адресом в памяти столько же последовательных адресов, сколько элементов в массиве
  3. включая нулевой символ завершения '\0' &str, &str[0]и strвсе три представляют одно и то же место в памяти, которое является адресом первого элемента массиваstr

    char * strPtr = & str [0]; // объявление и инициализация

Кроме того, вы можете разделить это на две части:

char *strPtr; strPtr = &str[0];
  1. strPtr это указатель на char
  2. strPtr указывает на массив str
  3. strPtr переменная со своим адресом в памяти
  4. strPtr переменная, которая хранит значение адреса &str[0]
  5. strPtr собственный адрес в памяти отличается от адреса памяти, который он хранит (адрес массива в памяти aka & str [0])
  6. &strPtr представляет адрес самого strPtr

Я думаю, что вы могли бы объявить указатель на указатель как:

char **vPtr = &strPtr;  

объявляет и инициализирует адресом указателя strPtr

В качестве альтернативы вы можете разделить на две части:

char **vPtr;
*vPtr = &strPtr
  1. *vPtr указывает на указатель strPtr
  2. *vPtr переменная со своим адресом в памяти
  3. *vPtr переменная, которая хранит значение адреса & strPtr
  4. Последний комментарий: вы не можете сделать str++, strадрес есть const, но вы можете сделатьstrPtr++
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.