2016-09-09 5 views
27

Вопрос простой и простой, s - это строка, мне вдруг пришла в голову идея попытаться использовать printf(s), чтобы увидеть, будет ли это работать, и я получил предупреждение в одном случае, а в другом - нет.В чем разница между printf (s) и printf («% s», 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) и почему я получаю предупреждение только в одном случае?

+0

Действительно удивительно, и очень интересно. Я подтверждаю это поведение, может быть объяснение, но пока кто-то не объяснит это, я считаю его ошибкой в ​​диагностике. –

+0

Подумайте немного, что может случиться, если у вас есть вторая строка со встроенным кодом формата и не передайте аргумент. Затем читайте о [* undefined behavior *] (https://en.wikipedia.org/wiki/Undefined_behavior). –

+1

@JoachimPileborg, так что вы говорите, что, не предоставляя строковый литерал, компилятор не имеет способа узнать, сколько аргументов потребуется? Поэтому, если я не предоставляю больше аргументов, я получаю предупреждение, но если я предоставляю хотя бы один, я этого не делаю. Я добавил еще один пример в вопросе, и я думаю, он поддерживает то, что я сказал. –

ответ

24

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

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

Первым является уязвимость 'string string'. Вы можете найти дополнительную информацию по этой теме.

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

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

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

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

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

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

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

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

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

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

+1

Я не вижу, как первые два абзаца объясняют это поведение. Строка формата может исходить от пользователя в обоих случаях. –

+0

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

+1

@MikaelMello более эффективным способом является использование 'puts'. Нет формата для синтаксического анализа, просто простой вывод символов –

6

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

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

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


Тем не менее, в отношении варианта предупреждения, -Wformat-securityfrom the online gcc manual

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

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

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


Обновление:

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

printf(s, 99, 50); 

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

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

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

+2

Извините, но, пожалуйста, прочитайте вопрос еще раз. –

+0

@iharob Я думаю, что я сделал это лучше, это правильно? –

+0

'printf (s)' и 'puts (s)' не являются эквивалентными. Во-первых, конечно, 'спецификаторы формата' printf' обрабатывают. Во-вторых, 'puts' добавляет новую строку. –

5

Есть две вещи в игре, в вашем вопросе.

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

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

EDIT: Я собираюсь дать дальнейший ответ на ваш вопрос, как средство добраться до небольшого мыльницы.

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

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

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

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

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

Или, еще проще, просто сделать:

printf("abcdefghij\n"); 

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

+0

Для вопросов 'const char *' vs 'char * const', обратитесь к http://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-and- int-const –

+0

Mmh. «Компилятор не может проверить, указали ли вы правильную сумму [аргументов]» - Но 'gcc' __can__: попробуйте с' printf («% d \ n»); 'или' printf ("% d \ n ", 123, 456);' (-Wformat и -Wformat-extra-args соответственно, оба устанавливаются -Wall на gcc 4.9.2 (по крайней мере, на моей системе)). – ilkkachu

+0

Кроме того, константа строки формата не входит в нее ... 'const char * s =" abcdefghij \ n "; printf (s); 'дает (с -Wformat-security):' printf.c: 8: 2: warning: формат не строковый литерал и аргументы формата [-Wformat-security] '- по-видимому (как говорится) он проверяет только содержимое строки формата, если это буквальный. Если бы я должен был догадаться, это деталь реализации (не обязательно следить за содержимым строки через программу для этого.) – ilkkachu

2

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

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

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

Узнайте больше о __attribute__here.

+1

«* IMHO он должен быть буквальным *» - нет необходимости в том, чтобы строка формата была буквально (хотя это вообще хорошая идея). –

+0

Конечно. Но это был прямой ответ на исходный вопрос: «Итак, какова разница между« printf (s) »и« printf »(«% s », s)» и почему я получаю предупреждение только в одном случае? » Вы получите предупреждение, потому что 'printf' объявляется с помощью' __attribute __ ((format (... 'AND, когда первый аргумент является литералом. Это происходит во время компиляции). Во время выполнения' printf' видит только указатель, независимо от того, переданный в форме ''% s "' или 's'. –

+0

Ух, это сбивает с толку как минимум. Строка формата не является _have_, чтобы быть буквальной, просто компилятор может ее проверить (это то, '__attribute __ ((format))' говорит ему делать), если это _is_ литерал. – ilkkachu

2

Так что основное различие между Е (ами) и Е ("% S", с)

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

Если атакующий контролирует «s», тогда это, вероятно, будет дырой в безопасности.

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

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

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

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

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

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

Смежные вопросы