В книге, которую я читаю, написано, что printf
использование одного аргумента (без спецификаторов преобразования) не рекомендуется. Рекомендуется заменить
printf("Hello World!");
с участием
puts("Hello World!");
или
printf("%s", "Hello World!");
Может кто подскажет, почему printf("Hello World!");
не так? В книге написано, что в нем есть уязвимости. Что это за уязвимости?
printf("Hello World!")
это не то же самое , какputs("Hello World!")
.puts()
добавляет'\n'
. Вместо этого сравнитеprintf("abc")
сfputs("abc", stdout)
printf
он устарел так же, как, например,gets
не рекомендуется в C99, поэтому вы можете подумать о том, чтобы отредактировать свой вопрос, чтобы быть более точным.Ответы:
printf("Hello World!");
IMHO не уязвим, но учтите это:Если
str
произойдет указание на строку, содержащую%s
спецификаторы формата, ваша программа будет демонстрировать неопределенное поведение (в основном сбой), тогдаputs(str)
как строка будет отображаться как есть.Пример:
источник
puts
, что предположительно будет быстрее.puts
"предположительно" быстрее, и это, вероятно, еще одна причина, по которой его рекомендуют, но на самом деле это не так . Я только что напечатал"Hello, world!"
1000000 раз в обе стороны. Сprintf
это ушло 0,92 секунды. Сputs
это ушло 0,93 секунды. Когда дело доходит до эффективности, есть о чем беспокоиться, ноprintf
vs.puts
не входит в их число.puts
быстрее» ложно, оно все равно ложно.puts
по этой причине. (Но если вы хотите поспорить об этом: я был бы удивлен, если бы вы могли найти любой современный компилятор для любой современной машины,puts
которая значительно быстрее, чемprintf
при любых обстоятельствах.)printf("Hello world");
в порядке и не имеет уязвимости в системе безопасности.
Проблема заключается в следующем:
где
p
- указатель на ввод, которым управляет пользователь. Он подвержен атакам на строки форматирования : пользователь может вставлять спецификации преобразования, чтобы получить контроль над программой, например,%x
для дампа памяти или%n
перезаписи памяти.Обратите внимание, что
puts("Hello world")
поведение не эквивалентно,printf("Hello world")
ноprintf("Hello world\n")
. Компиляторы обычно достаточно умны, чтобы оптимизировать последний вызов, чтобы заменить егоputs
.источник
printf(p,x)
было бы так же проблематично, если бы пользователь мог контролироватьp
. Таким образом, проблема заключается не в использованииprintf
одного аргумента, а в использовании строки формата, управляемого пользователем.printf(p)
, это потому, что они не понимают, что это строка формата, они просто думают, что печатают литерал.В дополнение к другим ответам,
printf("Hello world! I am 50% happy today")
это простая ошибка, потенциально вызывающая всевозможные неприятные проблемы с памятью (это UB!).Просто проще, проще и надежнее «требовать» от программистов, чтобы они были абсолютно ясны, когда им нужна дословная строка и ничего больше .
И это то, что
printf("%s", "Hello world! I am 50% happy today")
вас заводит. Это полностью надежно.(Стив, конечно
printf("He has %d cherries\n", ncherries)
, совершенно не то же самое; в данном случае программист не придерживается мышления «дословная строка»; она придерживается мышления «строка формата».)источник
printf
» - это примерно то же самое, что сказать «всегда писать»if(NULL == p)
. Эти правила могут быть полезны для некоторых программистов, но не для всех. И в обоих случаях (несоответствиеprintf
форматов и условные выражения Йоды) современные компиляторы все равно предупреждают об ошибках, так что искусственные правила еще менее важны.printf("%s", "hello")
будет медленнееprintf("hello")
, поэтому есть обратная сторона. Небольшой, потому что ввод-вывод почти всегда медленнее, чем такое простое форматирование, но это недостаток.gcc -Wall -W -Werror
предотвратит плохие последствия от таких ошибок.Я просто добавлю здесь немного информации об уязвимости .
Он считается уязвимым из-за уязвимости формата строки printf. В вашем примере, где строка жестко запрограммирована, она безвредна (даже если такие жесткие строки никогда не рекомендуется полностью). Но указывать типы параметров - хорошая привычка. Вот пример:
Если кто-то помещает символ строки формата в ваш printf вместо обычной строки (скажем, если вы хотите распечатать программу stdin), printf возьмет все, что сможет, в стек.
Он был (и остается) очень часто используемым для использования программ для исследования стеков, например, для доступа к скрытой информации или обхода аутентификации.
Пример (C):
если я поставлю как вход этой программы
"%08x %08x %08x %08x %08x\n"
Это дает указание функции printf извлечь пять параметров из стека и отобразить их в виде 8-значных шестнадцатеричных чисел с дополнениями. Таким образом, возможный результат может выглядеть так:
Смотрите это для более полного объяснения и других примеров.
источник
Вызов
printf
с использованием буквальных строк формата безопасен и эффективен, и существуют инструменты, которые автоматически предупреждают вас, если ваш вызовprintf
с использованием строк формата, предоставленных пользователем, небезопасен.Самые серьезные атаки на
printf
использование%n
спецификатора формата. В отличие от всех других спецификаторов формата, например%d
,%n
фактически записывает значение в адрес памяти, указанный в одном из аргументов формата. Это означает, что злоумышленник может перезаписать память и таким образом потенциально получить контроль над вашей программой. Википедия предоставляет более подробную информацию.Если вы вызываете
printf
с буквальной строкой формата, злоумышленник не сможет проникнуть%n
в вашу строку формата, и, таким образом, вы в безопасности. Фактически, gcc изменит ваш вызовprintf
на вызовputs
, поэтому практически нет никакой разницы (проверьте это, запустивgcc -O3 -S
).Если вы вызываете
printf
с помощью предоставленной пользователем строки формата, злоумышленник потенциально может проникнуть%n
в вашу строку формата и получить контроль над вашей программой. Ваш компилятор обычно предупреждает вас, что это небезопасно, см-Wformat-security
. Существуют также более продвинутые инструменты, которые гарантируют, что вызовprintf
безопасен даже с предоставленными пользователем строками формата, и они могут даже проверить, что вы передаете правильное количество и тип аргументовprintf
. Например, для Java есть Google's Error Prone и Checker Framework .источник
Это ошибочный совет. Да, если у вас есть строка времени выполнения для печати,
довольно опасно, и вы всегда должны использовать
вместо этого, потому что, как правило, никогда нельзя узнать,
str
может ли он содержать%
знак. Однако, если у вас есть постоянная строка времени компиляции , нет ничего плохого в(Среди прочего, это самая классическая программа на языке C, буквально из книги по программированию на языке C в Genesis. Так что любой, кто не одобряет такое использование, является довольно еретическим, и я, например, был бы несколько оскорблен!)
источник
because printf's first argument is always a constant string
Я не совсем понимаю, что вы имеете в виду."He has %d cherries\n"
это постоянная строка, что означает, что это константа времени компиляции. Но, честно говоря, совет автора заключался не в том, чтобы «не передавать константы в качествеprintf
первого аргумента», а в том, чтобы «не передавать строки без первого аргумента%
asprintf
».literally from the C programming book of Genesis. Anyone deprecating that usage is being quite offensively heretical
- вы фактически не читали K&R в последние годы. Там есть масса советов и стилей кодирования, которые в наши дни не только устарели, но и являются просто плохой практикой.int
ум приходит совет «никогда не использовать простые ».)Довольно неприятный аспект
printf
заключается в том, что даже на платформах, где случайное чтение из памяти может причинить только ограниченный (и приемлемый) вред, один из символов форматирования%n
приводит к интерпретации следующего аргумента как указатель на записываемое целое число и вызывает количество выведенных символов, которые будут сохранены в идентифицированной им переменной. Я сам никогда не использовал эту функцию, и иногда я использую облегченные методы в стиле printf, которые я написал, чтобы включать только те функции, которые я действительно использую (и не включать их или что-то подобное), но подавать стандартные строки функций printf, полученные из ненадежных источников может выявить уязвимости безопасности, помимо возможности чтения произвольного хранилища.источник
Поскольку никто не упомянул, я бы добавил примечание об их производительности.
При нормальных обстоятельствах, предполагая, что никакие оптимизации компилятора не используются (т.е.
printf()
фактически вызывает,printf()
а не нетfputs()
), я ожидалprintf()
бы работать менее эффективно, особенно для длинных строк. Это связано с тем,printf()
что необходимо проанализировать строку, чтобы проверить, есть ли какие-либо спецификаторы преобразования.Чтобы подтвердить это, я провел несколько тестов. Тестирование проводится на Ubuntu 14.04 с gcc 4.8.4. Моя машина использует процессор Intel i5. Тестируемая программа выглядит следующим образом:
Оба скомпилированы с использованием
gcc -Wall -O0
. Время измеряется с помощьюtime ./a.out > /dev/null
. Ниже приведен результат типичного запуска (я запускал их пять раз, все результаты в пределах 0,002 секунды).Для
printf()
варианта:Для
fputs()
варианта:Этот эффект усиливается, если у вас очень длинная струна.
Для
printf()
варианта (пробежал три раза, реальные плюс / минус 1,5 с):Для
fputs()
варианта (запускался трижды, реальные плюс / минус 0,2 с):Примечание: после проверки сборки, созданной gcc, я понял, что gcc оптимизирует
fputs()
вызов дляfwrite()
вызова даже с-O0
. (printf()
Вызов остается неизменным.) Я не уверен, аннулирует ли это мой тест, поскольку компилятор вычисляет длину строки дляfwrite()
во время компиляции.источник
fputs()
это часто используется со строковыми константами, и эта возможность оптимизации является частью того, что вы хотели сделать. При этом добавление тестового прогона с динамически сгенерированной строкой сfputs()
иfprintf()
было бы хорошей дополнительной точкой данных. ./dev/null
отчасти делает это игрушкой, поскольку обычно при генерации форматированного вывода ваша цель состоит в том, чтобы вывод куда-то направлялся, а не был отброшен. Как они будут сравниваться после того, как вы добавите время «фактически не отбрасывая данные»?автоматически компилируется в эквивалент
вы можете проверить это с помощью дизассемблирования исполняемого файла:
с помощью
приведет к проблемам с безопасностью, никогда не используйте printf таким образом!
так что ваша книга на самом деле правильная, использование printf с одной переменной устарело, но вы все равно можете использовать printf ("моя строка \ n"), потому что она автоматически станет put
источник
A compiles to B
, а на самом деле имеете в видуA and B compile to C
.Для gcc можно включить определенные предупреждения для проверки
printf()
иscanf()
.В документации gcc указано:
-Wformat
, Который включен в-Wall
опции не позволяет несколько специальных предупреждений , которые помогают найти эти случаи:-Wformat-nonliteral
выдаст предупреждение, если вы не передадите строковое буквенное обозначение в качестве спецификатора формата.-Wformat-security
предупредит, если вы передадите строку, которая может содержать опасную конструкцию. Это подмножество-Wformat-nonliteral
.Я должен признать, что включение
-Wformat-security
выявило несколько ошибок, которые у нас были в нашей кодовой базе (модуль регистрации, модуль обработки ошибок, модуль вывода xml, у всех были некоторые функции, которые могли бы делать неопределенные вещи, если бы они были вызваны с символами% в их параметре. Для информации, нашей кодовой базе сейчас около 20 лет, и даже если бы мы знали об этих проблемах, мы были чрезвычайно удивлены, когда включили эти предупреждения, сколько из этих ошибок все еще было в кодовой базе).источник
Помимо других хорошо объясненных ответов с затронутыми побочными проблемами, я хотел бы дать точный и краткий ответ на заданный вопрос.
printf
Вызов функции с одним аргументом в целом не устарела и не имеет также не уязвимостей при правильном использовании , как вы всегда должны кодировать.C Пользователи всего мира, от новичка до статусного эксперта, используют
printf
этот способ для вывода простой текстовой фразы на консоль.Более того, Кто-то должен различать, является ли этот единственный аргумент строковым литералом или указателем на строку, которая действительна, но обычно не используется. Для последнего, конечно, могут возникать неудобные выходные данные или любое неопределенное поведение , когда указатель не установлен должным образом, чтобы указывать на допустимую строку, но эти вещи также могут происходить, если спецификаторы формата не соответствуют соответствующим аргументам, давая несколько аргументов.
Конечно, также неправильно и неправильно, чтобы строка, предоставленная в качестве одного-единственного аргумента, имела какие-либо спецификаторы формата или преобразования, поскольку преобразование не произойдет.
Тем не менее, предоставление простого строкового литерала, такого
"Hello World!"
как единственный аргумент, без каких-либо спецификаторов формата внутри этой строки, как вы указали в вопросе:не является устаревшим и не является " плохой практикой ", и не имеет никаких уязвимостей.
Фактически, многие программисты на C начинают и начинают изучать и использовать C или даже языки программирования в целом с этой программы HelloWorld и этого
printf
оператора как первых в своем роде.Их бы не было, если бы они были устаревшими.
Что ж, тогда я сосредоточился бы на книге или на самом авторе. Если автор действительно делает такие, на мой взгляд, неправильные утверждения и даже преподает это, не объясняя явно, почему он / она это делает (если эти утверждения действительно буквально эквивалентны приведенным в этой книге), я бы счел это плохой книгой. Хорошая книга, в отличие от этого, должна объяснить , почему , чтобы избежать определенного рода программирования методов или функций.
Согласно тому, что я сказал выше, использование
printf
только с одним аргументом (строковым литералом) и без каких-либо спецификаторов формата ни в коем случае не считается устаревшим и не считается «плохой практикой» .Вы должны спросить автора, что он имел в виду, или, что еще лучше, попросите его уточнить или исправить соответствующий раздел для следующего издания или отпечатков в целом.
источник
printf("Hello World!");
это не эквивалентно вputs("Hello World!");
любом случае, что кое-что говорит об авторе рекомендации.