В C, почему% s работает, не задавая ему значение? - PullRequest
3 голосов
/ 22 июня 2019

По моим сведениям и некоторым темам вроде этого , если вы хотите напечатать строки в C, вы должны сделать что-то вроде этого:

printf("%s some text", value);

И значение будет отображаться вместо %s.

Я написал этот код:

char password[] = "default";
printf("Enter name: \n");
scanf("%s", password);
printf("%s is your password", password); // All good - the print is as expected

Но я заметил, что могу сделать то же самое без части значения, и она все равно будет работать:

printf("%s is your password");

Итак, мой вопрос: почему заполнитель %s получает значение, а я его не присваиваю, и как он узнает, какое значение ему дать?

Ответы [ 3 ]

9 голосов
/ 22 июня 2019

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

Стандарт говорит (подчеркнуто мое):

7.21.6.1 Функция fprintf

Функция fprintf записывает выходные данные в поток, на который указывает поток, под управлением строки, на которую указывает формат, которая указывает, как последующие аргументы преобразуются для вывода. Если для формата недостаточно аргументов, поведение не определено. Если формат исчерпан, а аргументы остаются, избыточные аргументы оцениваются (как всегда), но в противном случае игнорируются.Функция fprintf возвращает, когда встречается конец строки формата.
4 голосов
/ 22 июня 2019

Функция printf() использует функцию языка C, которая позволяет передавать переменное число аргументов функции.(Технически называемые «переменными функциями» - https://en.cppreference.com/w/c/variadic - я просто скажу «varargs» для краткости.)

Когда функция вызывается в C, аргументы функции помещаются вstack (*) - но дизайн функции varargs не позволяет вызываемой функции узнать, сколько параметров было передано.

Когда выполняется функция printf(), она сканирует строку формата и% s говорит ему искать строку в следующей позиции в списке аргументов переменной.Поскольку больше не содержит аргументов в списке, код «уходит с конца массива» и захватывает следующее, что он видит в памяти.Я подозреваю, что происходит то, что следующее место в памяти все еще имеет адрес password от вашего предыдущего вызова к scanf, и так как этот адрес указывает на строку, и вы сказали printf напечатать строку, вы получилиповезло, и это сработало.

Попробуйте выполнить другой вызов функции (например: printf("%s %s %s\n","X","Y","Z") между вызовами scanf("%s", password); и printf("%s is your password");, и вы почти наверняка увидите другое поведение.

Бесплатный совет: C имеетмного острых углов и неопределенных битов, но хороший компилятор (и инструмент статического анализа или 'lint') может предупредить вас о многих распространенных ошибках. Если вы собираетесь работать в C, узнайте, как проверять предупреждения вашего компилятораМаксимум, изучите, что означают все ошибки и предупреждения (как они происходят, а не все сразу!), и заставьте себя писать код на Си, который компилируется без каких-либо предупреждений. Это избавит вас от лишних хлопот.

(*) Обобщая здесь для простоты - иногда аргументы могут быть переданы в регистрах, иногда что-то встроено, бла-бла-бла.

3 голосов
/ 22 июня 2019

Итак, есть много сообщений о том, что вы не должны делать printf("%s is your password");, и что вам просто повезло.По твоему вопросу, я полагаю, ты это знал.Но мало кто говорит вам вероятную причину , почему вам повезло.

Чтобы понять, что, вероятно, произошло, мы должны понять, как передаются параметры функции.Вызывающая функция должна поместить параметры в согласованное место, чтобы функция могла найти параметры.Поэтому для параметров 1 ... N мы называем эти места r1 ... rN.(Такое соглашение является частью того, что мы называем «соглашением о вызове функций»)

Это означает, что этот код:

scanf("%s", password);
printf("%s is your password",password);

может быть преобразован компилятором в этот псевдокод

r1="%s";
r2=password;
call scanf;

r1="%s is your password";
r2=password;
call printf;

Если вы теперь удалите второй параметр из вызова printf, ваш псевдокод будет выглядеть следующим образом:

r1="%s";
r2=password;
call scanf;

r1="%s is your password";
call printf;

Помните, что после call scanf;, r2 может быть неизменным и все равно иметь значение password, поэтому call printf; "работает"

Вы можете подумать, что обнаружили новый способ оптимизации кода, исключив одно из r2=password; назначений,Это может быть верно для старых «тупых» компиляторов, но не для современных.

Современные компиляторы уже будут делать это, когда это безопасно.И это не всегда безопасно.Причины, по которым это небезопасно, могут заключаться в том, что scanf и printf имеют разные соглашения о вызовах, r2 могли быть изменены за вашей спиной и т. Д.

Чтобы лучше понять, чтокомпилятор делает, я рекомендую посмотреть выходные данные ассемблера вашего компилятора, на разных уровнях оптимизации.

И, пожалуйста, всегда компилируйте с -Wall.Компилятор часто хорошо говорит вам, когда вы делаете глупости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...