В первом случае строка с нелитеральным форматированием может исходить из кода пользователя или предоставленных пользователем (во время выполнения) данных, и в этом случае он может содержать %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
)
Действительно удивительно, и очень интересно. Я подтверждаю это поведение, может быть объяснение, но пока кто-то не объяснит это, я считаю его ошибкой в диагностике. –
Подумайте немного, что может случиться, если у вас есть вторая строка со встроенным кодом формата и не передайте аргумент. Затем читайте о [* undefined behavior *] (https://en.wikipedia.org/wiki/Undefined_behavior). –
@JoachimPileborg, так что вы говорите, что, не предоставляя строковый литерал, компилятор не имеет способа узнать, сколько аргументов потребуется? Поэтому, если я не предоставляю больше аргументов, я получаю предупреждение, но если я предоставляю хотя бы один, я этого не делаю. Я добавил еще один пример в вопросе, и я думаю, он поддерживает то, что я сказал. –