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

147

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

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

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

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

scanf( "%s" , &string );

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

abeln
источник
Во втором случае на самом деле возможного переполнения буфера нет, так как вы вообще не используете этот буфер. Либо так, либо вы можете сказать, что любая строка длиной более 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++
Angelita
источник