Для списка символов[3][10]; почему все они работают как аргументы scanf() %s ---&list[i],list[i],&list[i][0]?

Разве char* не является единственным допустимым типом аргумента для спецификатора формата %s, используемого в строке спецификатора формата scanf()? Если да, то почему в моей программе каждый из них работает одинаково для %s scanf() и printf(). описатель формата strong>:

scanf("%s",&list[i]);
scanf("%s",list[i]);   
scanf("%s",&list[i][0]);

Буду признателен, если вы устраните следующие недоразумения, вытекающие из этой предпосылки:

1) Почему &name[i] работает, если он имеет тип char (*)[]. Разве &name[i][0] не является единственным допустимым аргументом, поскольку он имеет тип char*?

2) Превращается ли name[i] в char*, когда мы передаем его в качестве аргумента для %s? Это то же самое, что и передача &name[i][0], которая является адресом первого символа каждого имени, которое мы вводим? Почему? оно работает?

#include<stdio.h>
#include<string.h>

int main(void)
{

char list[3][10];
int i;
printf("Enter the three names \n");

for(i=0;i<=2;i++)
scanf("%s",&list[i]);  //Why this works?
//scanf("%s",list[i]);   //Does it decompose into char* type?
//scanf("%s",&list[i][0]);

for(i=0;i<=2;i++)
printf("%s\n",list+i);  //All of these printf() work as well
//printf("%s\n",list[i]);
//printf("%s\n",&list[i][0]);

}

person Rüppell's Vulture    schedule 13.05.2013    source источник


Ответы (3)


list[i] является char[10] и подвергается преобразованию массива в указатель при передаче в качестве аргумента, поэтому в этом месте он полностью эквивалентен &list[i][0].

&list[i] является char(*)[10] и поэтому является недопустимым аргументом для спецификатора преобразования %s в printf или scanf, вызывая неопределенное поведение.

Однако, поскольку первый байт в list[i] имеет тот же адрес, что и массив list[i], обычно это работает(1), так как printf и scanf не знают о типах своих аргументов и интерпретируют их согласно строка формата.

(1) Это не удастся, если представление char* отличается от представления char(*)[10]. Это было бы необычно, но возможно.

person Daniel Fischer    schedule 13.05.2013

Списки переменных аргументов — например, используемые для printf() или scanf()не могут проверять свои переменные аргументы на правильность типов. Наоборот, эти функции полагаются на строку формата, указывающую правильный тип, поскольку строка формата определяет количество и тип аргументов, извлекаемых из стека.

(Подробности о том, как работают списки переменных аргументов, см. в документации к <stdargs.h>.)

Если вы укажете "%s" в качестве спецификатора формата, scanf() извлечет char * из стека и запишет стандартный ввод по этому адресу, независимо от того, что вы на самом деле указали в качестве параметра, и дали ли вы параметр вообще .

Излишне говорить, что если ваши спецификаторы формата не соответствуют вашим аргументам по количеству, типу и порядку, вам может повезти и вы действительно получите результат, но вы определенно вызываете неопределенное поведение.

(Это говорит об общем вопросе «почему это не дает мне ошибку», предполагая, что у вас нет современного компилятора и включено соответствующее предупреждение, например -Wformat для GCC. Что касается преобразований типов в вашем конкретный случай, см. ответ Даниэля.)


PS: Пока мы этим занимаемся, использование scanf() в сочетании с %s в любом случае довольно опасно, потому что слишком длинный ввод забьет вашу программу. По крайней мере, ограничьте ввод, например. через %10s. Еще лучше читать входные данные через fgets() и выполнять правильный разбор входных данных в памяти, поскольку возможности scanf() корректно восстанавливать искаженные входные данные сильно ограничены.

person DevSolar    schedule 13.05.2013

Пока аргумент предоставляет правильный адрес, он будет «делать правильные вещи». Однако, если вы усложните пример или используете их для аргументов функций, которые принимают определенные аргументы, то вы обнаружите, что некоторые из них не являются «правильными».

Аргумент scanf при использовании формата %s должен быть адресом массива символов с достаточным пространством для хранения читаемой строки. В вашем примере &list[i] фактически этого не обеспечивает, это адрес указателя на массив символов. Так уж получилось, что этот адрес совпадает с адресом массива символов, представляющего массив. &list[i][0] — это указатель на один символ, но за ним следует еще 9, так что это «работает», но опять же технически неправильно. list[i] - единственный абсолютно правильный вариант. Но все альтернативы имеют один и тот же адрес, поэтому технически в этом случае они будут работать.

Когда дело доходит до printf, нам снова нужен адрес массива символов.

В этом случае и list+i, и list[i] означают одно и то же: когда массив используется таким образом, list делает его указателем на первый элемент, а +n дает вам n-й элемент, как и в скобках. &list[i][0] дает адрес одного символа, но это тот же адрес, что и первый элемент всего массива, поэтому будет получен тот же результат.

Между прочим, я не думаю, что gcc, который понимает строку формата и МОЖЕТ проверять и выдавать предупреждения о смешении строки формата и соответствующих аргументов, будет жаловаться на большинство этих форм. Но не все формы технически правильны.

person Mats Petersson    schedule 13.05.2013
comment
Если модификатор длины l отсутствует, соответствующий аргумент должен быть указателем на начальный элемент массива символов, &list[i][0] совершенно правильно (при условии, что ввод достаточно короткий). Здесь нет разницы между list[i] и &list[i][0]. В этом случае и list+i, и list[i] означают одно и то же. Нет, list + i — это char(*)[10], а list[1] преобразуется в char* (&list[i][0]). list + i обычно работает, но это неправильный тип. - person Daniel Fischer; 13.05.2013