Подтвердить что ты не робот

В чем разница между printf (s) и printf ( "% s", s)?

Вопрос простой и простой, s - это строка, мне вдруг пришла в голову идея использовать printf(s), чтобы увидеть, будет ли она работать, и я получил предупреждение в одном случае, а в другом - нет.

char* s = "abcdefghij\n";
printf(s);

// Warning raised with gcc -std=c11: 
// format not a string literal and no format arguments [-Wformat-security]

// On the other hand, if I use 

char* s = "abc %d efg\n";
printf(s, 99);

// I get no warning whatsoever, why is that?

// Update, I've tested this:
char* s = "random %d string\n";
printf(s, 99, 50);

// Results: no warning, output "random 99 string".

Итак, какова разница между printf(s) и printf("%s", s) и почему я получаю предупреждение только в одном случае?

4b9b3361

Ответ 1

В первом случае строка с нелитеральным форматированием, возможно, исходит из пользовательского кода или предоставленных пользователем (во время выполнения) данных, и в этом случае он может содержать %s или другие спецификации преобразования, для которых вы не передал данные. Это может привести ко всем видам проблем с чтением (и писать проблемы, если строка содержит %n - см. printf() или ваши страницы руководства библиотеки C).

Во втором случае строка формата управляет выводом, и не имеет значения, содержит ли какая-либо строка для печати спецификации преобразования или нет (хотя в показанном коде печатается целое число, а не строка). Компилятор (GCC или Clang используется в вопросе) предполагает, что, поскольку есть аргументы после строки (нелиберального) формата, программист знает, к чему они относятся.

Во-первых, это уязвимость строки формата. Вы можете найти дополнительную информацию по этой теме.

GCC знает, что в большинстве случаев единственный аргумент printf() с нелитеральной строкой формата является приглашением на неприятности. Вместо этого вы можете использовать puts() или fputs(). Достаточно опасно, что GCC генерирует предупреждения с минимальной провокацией.

Более общая проблема нелитеральной строки форматирования также может быть проблематичной, если вы не будете осторожны, но чрезвычайно полезны, если будете осторожны. Вам нужно больше работать, чтобы заставить GCC жаловаться: для получения жалобы требуется -Wformat и -Wformat-nonliteral.

Из комментариев:

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

Из трех ваших операторов printf(), учитывая ограниченный контекст, что переменная s назначается непосредственно над вызовом, нет реальной проблемы. Но вы можете использовать puts(s), если вы опустили новую строку из строки или fputs(s, stdout), как она есть, и получите тот же результат, без накладных расходов printf(), разбора всей строки, чтобы выяснить, что все простые символы печатается.

Второй оператор printf() также безопасен, как указано; строка формата соответствует переданным данным. Между этим нет существенной разницы и просто передать строку формата как литерал - за исключением того, что компилятор может больше проверить, является ли строка формата литералом. Результат выполнения тот же.

Третий printf() передает больше аргументов данных, чем требуется для форматирования строки, но это доброкачественно. Однако это не идеально. Опять же, компилятор может лучше проверить, если строка формата является литералом, но эффект времени выполнения практически тот же.

В спецификации printf(), связанной сверху:

Каждая из этих функций преобразует, форматирует и печатает свои аргументы под управлением формата. Формат - это символьная строка, начинающаяся и заканчивающаяся в исходном состоянии сдвига, если таковая имеется. Формат состоит из нулей или более директив: обычные символы, которые просто копируются в выходной поток, и спецификации преобразования, каждая из которых должна приводить к выбору нулевых или более аргументов. Результаты undefined, если для формата недостаточно аргументов. Если формат исчерпан, пока аргументы остаются, избыточные аргументы должны быть оценены, но в противном случае игнорируются.

Во всех этих случаях нет четкого указания того, почему строка формата не является литералом. Однако одной из причин нежелания нелитеральной строки форматирования может быть то, что иногда вы печатаете числа с плавающей запятой в нотации %f и иногда в нотации %e, и вам нужно выбрать, во время выполнения. (Если это просто основано на значении, %g может быть уместным, но есть моменты, когда требуется явный контроль - всегда %e или всегда %f.)

Ответ 2

Предупреждение говорит все.

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

Таким образом, более чистый (или более безопасный) подход (для печати строки, которая не требует спецификации формата) будет puts(s); поверх printf(s); (первая не обрабатывает s для любого спецификаторы преобразования, устраняя причину возможного UB в последнем случае). Вы можете выбрать fputs(), если вас беспокоит окончание новой строки, которое автоматически добавляется в puts().


Что касается опции предупреждения, -Wformat-security из руководства онлайн gcc

В настоящее время это предупреждает о вызовах функций printf и scanf, где строка формата не является строковым литералом, и нет аргументов формата, как в printf (foo);. Это может быть дыра в безопасности, если строка формата поступает из ненадежного ввода и содержит %n.

В вашем первом случае в printf() имеется только один аргумент, который не является строковым литералом, а скорее переменной, которая может быть очень хорошо сгенерирована/заполнена во время выполнения, и если в ней содержатся неожиданные спецификаторы формата, может вызвать UB. У компилятора нет возможности проверить наличие в нем какого-либо спецификатора формата. Это проблема безопасности.

Во втором случае прилагаемый аргумент предоставляется, спецификатор формата не является единственным аргументом, переданным в printf(), поэтому первый аргумент не нуждается в проверке. Следовательно, предупреждения не существует.


Update:

Относительно третьего, с избыточным аргументом, который требуется в прилагаемой строке формата

printf(s, 99, 50);

цитирование из C11, глава §7.21.6.1

[...] Если формат исчерпан, а аргументы остаются, избыточные аргументы (как всегда), но в противном случае игнорируются. [...]

Итак, передача лишнего аргумента не является проблемой (с точки зрения компилятора) вообще, и она четко определена. НЕТ области для любого предупреждения.

Ответ 3

В вашем вопросе есть две вещи.

Первое покрывается лаконично Джонатаном Леффлером - предупреждение, которое вы получаете, состоит в том, что строка не является буквальной и не имеет в ней каких-либо спецификаторов формата.

Другой - тайна того, почему компилятор не выдал предупреждение о том, что количество аргументов не совпадает с количеством спецификаторов. Короткий ответ - "потому что это не так", но, более конкретно, printf является вариационной функцией. Он принимает любое количество аргументов после спецификации исходного формата - от 0 до. Компилятор не может проверить, указали ли вы правильную сумму; что до самой функции printf и приводит к поведению undefined, которое Йоахим упомянул в комментариях.

ИЗМЕНИТЬ: Я собираюсь дать дальнейший ответ на ваш вопрос, как средство добраться до небольшого soapbox.

В чем разница между printf(s) и printf("%s", s)? Простой - в последнем вы используете printf, как было объявлено. "%s" является const char *, и в дальнейшем он не будет генерировать предупреждающее сообщение.

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

Ваша проблема может быть решена несколькими способами.

const char* s = "abcdefghij\n";
printf(s);

разрешит предупреждение, потому что теперь вы используете указатель const, и нет никаких опасностей, которые упоминал Джонатан. (Вы также можете объявить его как const char* const s, но этого не нужно. Первый const важен, поскольку он соответствует объявлению printf, а потому, что const char* s означает, что символы, на которые указывает s, могут 't change, т.е. строка является литералом.)

Или, еще проще, просто выполните:

printf("abcdefghij\n");

Это неявно является указателем const, а также не является проблемой.

Ответ 4

Основная причина: printf объявляется как:

int printf(const char *fmt, ...) __attribute__ ((format(printf, 1, 2)));

Это говорит gcc, что printf - это функция с интерфейсом типа printf, где сначала начинается строка формата. ИМХО это должно быть буквальным; Я не думаю, что есть способ сказать хорошему компилятору, что s на самом деле является указателем на литеральную строку, которую он видел раньше.

Подробнее о __attribute__ здесь.

Ответ 5

Итак, какова разница между printf (s) и printf ( "% s", s)

"printf (s)" будет обрабатывать s как строку формата. Если s содержит спецификаторы формата, printf будет их интерпретировать и искать varargs. Поскольку на самом деле нет varargs, это, скорее всего, вызовет поведение undefined.

Если злоумышленник контролирует "s", это скорее всего будет дырой в безопасности.

printf ( "% s", s) просто напечатает то, что находится в строке.

и почему я получаю предупреждение только в одном случае?

Предупреждения - это баланс между улавливанием опасной глупости и не созданием слишком большого шума.

Программисты

C имеют привычку использовать printf и различные функции, подобные printf *, как общие функции печати, даже если на самом деле им не требуется форматирование. В этой среде человеку легко ошибиться при написании printf (ов), не задумываясь о том, откуда пришли. Поскольку форматирование довольно бесполезно без каких-либо данных для форматирования printf (s), имеет мало законного использования.

printf (s, format, arguments), с другой стороны, указывает на то, что программист намеренно планировал форматирование.

Признайте, что это предупреждение не включено по умолчанию в upstream gcc, но некоторые дистрибутивы включили его как часть своих усилий по сокращению дыр в безопасности.

* Обе стандартные функции C, такие как sprintf и fprintf, и функции в сторонних библиотеках.