Семейство функций printf в языке программирования C представляет собой набор функций , которые принимают строку формата в качестве входных данных среди списка других значений переменного размера и создают в качестве выходных данных строку , соответствующую спецификатору формата и заданным входным значениям. Строка написана на простом языке шаблонов : символы обычно копируются в выходные данные функции буквально, но спецификаторы формата , которые начинаются с %символа, указывают местоположение и метод преобразования фрагмента данных (например, числа) в символы. Дизайн был скопирован, чтобы предоставить аналогичную функциональность в других языках программирования .
«printf» — это имя одной из основных функций вывода C и означает « формат печати ». Строки формата printf дополняют строки формата scanf , которые обеспечивают форматированный ввод ( лексический анализ или синтаксический анализ ). В обоих случаях они обеспечивают простую функциональность и фиксированный формат по сравнению с более сложными и гибкими механизмами шаблонов или лексерами/парсерами, но достаточны для многих целей.
Многие языки, кроме C, точно или точно копируют синтаксис строки формата printf в своих собственных функциях ввода-вывода.
Несоответствие между спецификаторами формата и типом данных может привести к сбоям и другим уязвимостям. Строка формата сама по себе очень часто является строковым литералом , что позволяет проводить статический анализ вызова функции. Однако это также может быть значение переменной, которая допускает динамическое форматирование, а также создает уязвимость безопасности, известную как эксплойт неконтролируемой строки формата .
Ранние языки программирования, такие как Фортран, использовали специальные операторы с синтаксисом, совершенно отличным от синтаксиса других вычислений, для создания описаний форматирования. В этом примере формат указан в строке 601, а команда WRITE ссылается на него по номеру строки:
ЗАПИСЬ ВЫХОДНОЙ ЛЕНТЫ 6 , 601 , IA , IB , IC , AREA 601 ФОРМАТ ( 4 H A = , I5 , 5 H B = , I5 , 5 H C = , I5 , & 8 H AREA = , F10 . 2 , 13 H КВАДРАТНЫЕ ЕДИНИЦЫ )
АЛГОЛ 68 имел более функциональный API , но по-прежнему использовал специальный синтаксис ( $
разделители окружают специальный синтаксис форматирования):
printf (( $ "Цвет " g ", число1 " 6 d , ", число2 " 4 zd , ", hex " 16 r2d , ", float " - d .2 d , ", беззнаковое значение " -3 d "." l$ , "красный" , 123456 , 89 , БИН 255 , 3.14 , 250 ));
Но использование обычных вызовов функций и типов данных упрощает язык и компилятор и позволяет писать реализацию ввода/вывода на одном и том же языке. Эти преимущества перевешивают недостатки (например, полное отсутствие типовой безопасности во многих случаях), и в большинстве новых языков ввод-вывод не является частью синтаксиса.
Язык C printf
берет свое начало в функции BCPLwritef
(1966). По сравнению с C
и является escape-последовательностью языка BCPL printf
, представляющей символ новой строки (для которого C использует escape-последовательность ), а порядок ширины и типа поля спецификации формата изменен на обратный : [1]*N
\n
writef
WRITEF("Проблема %I2-QUEENS ИМЕЕТ %I5 РЕШЕНИЙ*N", NUMQUEENS, COUNT)
Вероятно, первым копированием синтаксиса за пределами языка C была printf
команда оболочки Unix, впервые появившаяся в Версии 4 , как часть порта на C. [2]
Форматирование осуществляется с помощью заполнителей внутри строки формата. Например, если программа хочет распечатать возраст человека, она может представить результат, добавив к нему префикс «Ваш возраст» и используя десятичный символ со знаком, d
чтобы обозначить, что мы хотим, чтобы целое число для возраста отображалось немедленно. после этого сообщения мы можем использовать строку формата:
printf ( "Ваш возраст %d" , age );
Синтаксис заполнителя формата:
%[ параметр ][ флаги ][ ширина ][. точность ][ длина ] тип
Это расширение POSIX , а не C99 . Поле Параметр может быть опущено или может быть:
Эта функция в основном используется при локализации, где порядок появления параметров варьируется в зависимости от языка.
В Microsoft Windows, отличной от POSIX, поддержка этой функции вынесена в отдельную функцию printf_p.
Поле Флаги может содержать ноль или более (в любом порядке):
Поле «Ширина» определяет минимальное количество выводимых символов и обычно используется для заполнения полей фиксированной ширины в табличном выводе, где в противном случае поля были бы меньше, хотя это не приводит к усечению полей слишком большого размера.
Поле ширины может быть опущено или может быть числовым целочисленным значением или динамическим значением, если оно передается в качестве другого аргумента, если оно отмечено звездочкой *
. [3] Например, printf("%*d", 5, 10)
будет 10
напечатано сообщение общей шириной 5 символов.
Хотя ведущий ноль не является частью поля ширины, он интерпретируется как флаг заполнения нулями, упомянутый выше, а отрицательное значение рассматривается как положительное значение в сочетании с флагом выравнивания по левому краю, -
также упомянутым выше.
Поле «Точность» обычно указывает максимальный предел вывода в зависимости от конкретного типа форматирования. Для числовых типов с плавающей запятой он указывает количество цифр справа от десятичной точки, на которое следует округлять выходные данные. Для строкового типа он ограничивает количество выводимых символов, после чего строка усекается.
Поле точности может быть опущено или может быть числовым целочисленным значением или динамическим значением, если оно передается в качестве другого аргумента, если оно отмечено звездочкой *
. Например, printf("%.*s", 3, "abcdef")
приведет к abc
печати.
Поле «Длина» может быть опущено или может иметь любое из следующих значений:
Кроме того, до широкого использования расширений ISO C99 существовало несколько вариантов длины для конкретной платформы:
ISO C99 включает inttypes.h
заголовочный файл, который включает ряд макросов для использования в независимом от платформы printf
кодировании. Они должны быть вне двойных кавычек, напримерprintf("%" PRId64 "\n", t);
Примеры макросов включают в себя:
Поле Тип может быть любым из:
Существует несколько реализаций printf
-подобных функций, которые позволяют расширять мини-язык на основе escape-символов , что позволяет программисту иметь специальную функцию форматирования для невстроенных типов. Одной из самых известных является (сейчас устаревшая) функция glibc Register_printf_function(). Однако он используется редко из-за того, что конфликтует с проверкой статической строки формата. Другой вариант — специальные средства форматирования Vstr, которые позволяют добавлять имена многосимвольных форматов.
Некоторые приложения (например, HTTP-сервер Apache ) включают в себя собственную printf
функцию -like и встраивают в нее расширения. Однако все они, как правило, имеют одни и те же проблемы register_printf_function()
.
Функция ядра Linux printk
поддерживает несколько способов отображения структур ядра с использованием общей %p
спецификации путем добавления дополнительных символов формата. [8] Например, %pI4
печатает адрес IPv4 в десятичном формате с точками. Это позволяет проверять статическую форматную строку (части %p
) за счет полной совместимости с обычным printf.
В большинстве языков, имеющих printf
функцию -подобную, недостаток этой функции обходится простым использованием %s
формата и преобразованием объекта в строковое представление.
Если для предоставления значений для всех спецификаций преобразования в строке шаблона предоставлено слишком мало аргументов функции или если аргументы имеют неправильные типы, результаты не определены и программа может завершиться сбоем. Реализации несовместимы в отношении того, используют ли синтаксические ошибки в строке аргумент и какой тип аргумента они используют. Лишние аргументы игнорируются. В ряде случаев неопределенное поведение приводило к уязвимостям безопасности « Атака на строку формата » . В большинстве соглашений о вызовах C или C++ аргументы могут передаваться в стек, что означает, что в случае слишком малого количества аргументов printf прочитает конец текущего кадра стека, что позволит злоумышленнику прочитать стек.
Некоторые компиляторы, такие как GNU Compiler Collection , статически проверяют строки формата функций, подобных printf, и предупреждают о проблемах (при использовании флагов -Wall
или -Wformat
). GCC также будет предупреждать о определяемых пользователем функциях в стиле printf, если __attribute__
к функции применяется нестандартный «формат» .
Использование только ширины поля для табуляции, как в случае с форматом %8d%8d%8d
трех целых чисел в трех 8-символьных столбцах, не гарантирует, что разделение полей будет сохранено, если в данных встречаются большие числа:
1234567 1234567 1234567123 123 123 123 12345678123
Потеря разделения полей может легко привести к повреждению выходных данных. В системах, которые поощряют использование программ в качестве строительных блоков в сценариях, такие поврежденные данные часто могут быть перенаправлены и испорчены для дальнейшей обработки, независимо от того, ожидал ли первоначальный программист, что выходные данные будут прочитаны только человеческими глазами. Такие проблемы можно устранить, включив явные разделители, даже пробелы, во все форматы табличного вывода. Просто изменив предыдущий опасный пример, чтобы %7d %7d %7d
решить эту проблему, форматируйте одинаково, пока числа не станут больше, но затем явно предотвращайте их объединение при выводе из-за явно включенных пробелов:
1234567 1234567 1234567123 123 123 123 12345678 123
Аналогичные стратегии применяются к строковым данным.
Несмотря на то, что функция вывода на первый взгляд, printf
позволяет записывать в ячейку памяти, указанную аргументом через %n
. Эта функция иногда используется как часть более сложных атак с использованием форматной строки. [9]
Эта %n
функциональность также делает printf
случайную Тьюринг-полной даже при правильно сформированном наборе аргументов. Игра в крестики-нолики, записанная в формате string, стала победителем 27-го IOCCC . [10]
В этот список не включены языки, использующие строки формата, отличающиеся от стиля, описанного в этой статье (например, AMPL и Elixir ), языки, которые наследуют свою реализацию от JVM или другой среды (например, Clojure и Scala ), а также языки, которые не иметь стандартной собственной реализации printf, но иметь внешние библиотеки, которые эмулируют поведение printf (например, JavaScript ).
%
оператора) [13]printf
, sprintf
и fmt
)print()
и FileStream.printf()
)printf
команда, иногда встроенная в оболочку, например, в некоторых реализациях оболочки KornShell (ksh), оболочки Bourne Again (bash) или оболочки Z (zsh). Эти команды обычно интерпретируют escape-символы C в строке формата.iostream
printf
(Юникс)printk
(печатать сообщения ядра)scanf