Читання рядка з scanf


147

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

(не маючи на увазі можливого переповнення буфера, це просто простий приклад)

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

Однак, здається, працює і наступне,

scanf( "%s" , &string );

Це просто мій компілятор (gcc), чиста удача чи щось інше?


У другому випадку насправді немає можливого переповнення буфера, оскільки ви взагалі не використовуєте цей буфер. Або це, або ви можете сказати, що будь-який рядок розміром більше 3 символів переповнить ваш "буфер".
ТЕД

Я мав на увазі перший приклад. Також інші вже вказали, що тут відбувається.
abeln

Так. Спробував це, і Гарет має рацію. Дивно.
ТЕД

Оскільки це запитання повертається шляхом пошуку "як прочитати рядок з 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 на символіці "машини Ліса" зробила щось подібне.)


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 і thatfe 12fe60. Нарешті & strPtr - адреса змінної strPtr, це не значення значень у strptr, а фактична адреса пам'яті strPtr. Оскільки strptr є різною змінною, ніж усі інші, вона також має іншу адресу пам'яті, в цьому випадку 12fe54
Juan Besa

-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 є вказівником на a 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.