printf
Материал из Википедии — свободной энциклопедии
printf — функция вывода в поток стандартного вывода значений переменных разных типов, отформатированных согласно заданному шаблону.
В общем случае так называют целое семейство функций, различающихся по различным второстепенным особенностям. Например, fprintf записывает вывод в файл, sprintf помещает вывод в строку, vprintf принимает аргументы с помощью va list, snprintf, vsnprintf имеют дополнительный параметр, ограничивающий максимальный размер отформатированной строки.
Впервые функция появилась в предшественниках языка Си (BCPL и Би), однако полную свою форму (полноценнная строка форматирования с флагами, шириной, точностью и размером) она обрела в стандартной библиотеке Си, откуда попала в Си++ и Objective-C.
Аналогичный используемому в printf синтаксис строки шаблона вывода (называемой иногда строкой форматирования или строкой формата) в дальнейшем начал использоваться другими языками программирования (с учётом возможных изменений в рамках возможностей языка).
[править] История появления
Первые следы будущей функции printf
появляются в языке BCPL в 1960-х. Функция WRITEF
принимает строку форматирования, в которой тип данных указывается отдельно от самих данных в строковой переменной (тип указывался без полей флагов, ширины, точности и размера, но уже предварялся символом процента %).[1] Появившийся вслед за ним в 1969 году язык Би уже использовал название printf
с простейшей строкой форматирования (аналогичной BCPL), указывался только один из трёх возможных типов и два представления числа: десятичные (%d), восьмеричные (%o), строки (%s) и символы (%c).[2]
Основной целью появления строки форматирования была передача типов аргументов, так как для языков программирования со статической типизацией определение типа переданного аргумента для функции с произвольными типами аргументов требует сложного и неэффективного (по крайней мере во времена создания Си) механизма RTTI.
Единственной возможностью по форматированию вывода в этих функциях было добавление символов до и после вывода значения переменной.
Сама функция WRITEF
была средством упрощения вывода, вместо набора функций WRCH
(вывод символа), WRITES
(вывод строки), WRITEN
, WRITED
, WRITEOCT
, WRITEHEX
(вывод чисел в различной форме) использовался единый вызов, в котором можно было чередовать «просто текст» с выходными значениями.
[править] Использование в других языках программирования
Помимо Си и его производных (Си++, Objective-C), printf-подобный синтаксис строки форматирования используют многие другие языки программирования:
- Perl — sprintf[3]
- Python — форматирующий оператор '%'[4]
- PHP — функция printf[5]
- Java — функция java.io.PrintStream.printf()[6]
- Ruby — функция printf[7]
- TCL — функция format[8]
- GNU Octave — функция printf[9]
- Maple — функция printf (встроенный язык)[10]
- AMPL[11]
- Elisp[12]
- OCaml — функция printf[13]
Помимо этого, благодаря наличию утилиты printf в составе большинства UNIX-подобных систем, printf используется во многих shell-скриптах (для sh, bash, csh, zsh и т. д.).
[править] Именование функций семейства
Все функции имеют в имени основу printf. Префиксы перед именем функции означают:
- v (vprintf, vsnprintf и т.д.) — функция вместо переменного числа параметров принимает список аргументов va list.
- f (fprintf, vfprintf) — вывод результата в передаваемый через параметр функции поток, вместо стандартного вывода.
- s (sprintf, snprintf, vsprintf, vspnprintf) — запись результата в строку (буфер в памяти), а не поток.
- n (snprintf, vnsprintf) — наличие параметра, ограничивающего максимальное количество символов для записи результата (используется только вместе с префиксом s). В Maple функция nprintf аналогична sprintf, но возвращает не текстовую строку, а имя.
- w, перед остальными префиксами (wsprintf, wvsprintf, wnsprintf, wvnsprintf) — использующийся фирмой Microsoft префикс для реализаций семейства функций sprintf в операционных системах Windows.
- w, после остальных префиксов (fwprintf, swprintf, wprintf) — функция использует многобайтовую кодировку (wchar_t) вместо обычных строк. (при этом функция swprintf не имеет префикса «n», хотя и принимает параметр, ограничивающий размер результирующей строки).
- a (asprintf, vasprintf) — расширения GNU; функции аналогичные sprintf/vsprintf, но выделяющие достаточный объём памяти с помощью malloc для форматированной строки. Вместо указателя на строку эти функции принимают указатель на указатель на строку, освобождение памяти производит вызвавшая функция.
[править] Общие соглашения
Все функции в качестве одного из параметров (format) принимают строку форматирования (описание синтаксиса строки ниже). Возвращают количество записанных (выведенных) символов, не включая нулевой символ в конце. Количество аргументов, содержащих данные для форматированного вывода, должно быть не менее, чем упомянуто в строке форматирования. «Лишние» аргументы игнорируются.
Функции семейства n (snprintf, vsnprintf) возвращают количество символов, которое было бы выведено, если бы параметр n (ограничивающий количество выводимых символов) был достаточно большим. В случае однобайтовых кодировок, возвращаемое значение соответствует необходимому размеру строки (не включая нулевой символ в конце).
Функции семейства s (sprintf, snprintf, vsprintf, vsnprintf) первым параметром (s) принимают указатель на область памяти, куда будет записана результирующая строка. Функции, не имеющие ограничения по количеству записываемых символов являются небезопасными функциями, так как могут привести к ошибке переполнения буфера, в случае, если выводимая строка окажется больше, чем размер выделенной для вывода области памяти.
Функции семейства f записывают строку в любой открытый поток (параметр stream), в частности, в стандартные потоки вывода (stdout, stderr). fprintf(stdout, format, …)
эквивалентно printf(format, …)
.
Функции семейства v принимают аргументы не в виде переменного числа аргументов (как все остальные printf-функции), а в виде списка va list. При этом при вызове функции макрос va end не выполняется.
Функции семейства w (первым символом) являются ограниченной реализацией Microsoft семейства функций s: wsprintf, wnsprintf, wvsprintf, wvnsprintf. Эти функции реализованы в динамических библиотеках user32.dll и shlwapi.dll (n функции). Они не поддерживают вывод значений с плавающей запятой, кроме того, wnsprintf и wvnsprintf поддерживают выравнивание текста только по левому краю.
Функции семейства w (wprintf, swprintf) реализуют поддержку многобайтовых кодировок, все функции этого семейства работают с указателями на многобайтные строки (wchar_t).
Функции семейства a (asprintf, vasprintf) выделяют память для строки вывода при помощи функции malloc, освобождение памяти производится в вызвавшей процедуре, в случае ошибки при выполнении функции память не выделяется.
[править] Описание функций
[править] Имена параметров
- format — строка форматирования (формат описан ниже)
- stream — файловый поток для вывода
- s — строка для помещения результата работы функции
- n — переменная, содержащая максимальное допустимое количество символов для строки s
- ap — список значений для вывода
- strp — указатель на указатель на строку для помещения результатов работы функции
[править] Описание функций
int printf( const char *format, ... );
- Вывод форматированной строки на стандартный вывод
int fprintf( FILE *stream, const char *format, ... );
- Вывод форматированной строки в поток
int sprintf( char *s, const char *format, ... );
- Запись форматированной строки в строку без ограничения по размеру строки
int snprintf( char *s, size_t n, const char *format, ... );
- Запись форматированной строки в строку с ограничением по размеру строки
int vprintf( const char *format, va_list ap );
- Вывод форматированной строки на стандартный вывод, значения для вывода передаются в функцию в виде списка va list
int vfprintf( FILE *stream, const char *format, va_list ap );
- Запись форматированной строки в поток, значения для вывода передаются в функцию в виде списка va list
int vsprintf( char *s, const char *format, va_list ap );
- Запись форматированной строки в строку без ограничения размера; значения для вывода передаются в функцию в виде списка va list
int vsnprintf( char *s, size_t n, const char *format, va_list ap );
- Запись форматированной строки в строку с ограничением на количество выводимых символов. Значения для вывода передаются в функцию в виде списка va list
int fwprintf(FILE *stream, const wchar_t *format, ... );
- Запись форматированной строки многобайтовых символов в файл
int swprintf(wchar_t *ws, size_t n, const wchar_t *format, ... );
- Запись форматированной многобайтовой строки в область памяти (Примечание: несмотря на отсутствие буквы n в названии, эта функция принимает параметр, ограничивающий максимальный размер выходной строки).
int wprintf(const wchar_t *format, ... );
- Вывод многобайтовой форматированной строки на терминал
int wsprintf( LPTSTR s, LPCTSTR format, ... );
- Реализация функции sprintf в операционной системе Windows. В отличие от sprintf не поддерживает вывод значений с плавающей запятой, вывод указателей, дополнительно поддерживает обработку многобайтовых строк в однобайтовой версии функции), не поддерживает флаг '+'.
int wnsprintf( LPTSTR s, int n, LPCTSTR format, ... );
- Реализация функции snprintf в операционной системе Windows. Не поддерживает вывод значений с плавающей запятой и указателей, поддерживает только флаг выравнивания по левому краю.
int wvsprintf( LPTSTR s, LPCTSTR format, va_list ap );
int wvnsprintf( LPTSTR s, int n, LPCTSTR format, va_list ap );
int asprintf(char **strp, const char *format, ... );
- функция записывает результат в строку, память для которой выделяется при помощи malloc
int vasprintf(char **strp, const char *format, va_list ap);
- функция записывает результат в строку, память для которой выделяется при помощи malloc, значения для вывода передаются в функцию в виде списка va list
Возвращаемое значение: отрицательное значение - признак ошибки; в случае успеха функции возвращают количество записанных/выведенных байтов (без учёта нулевого байта в конце), функция snprintf выводит количество байт, которые были бы записаны, если бы n было бы достаточного размера.
При вызове snprintf, n может быть равно нулю (в этом случае s может быть нулевым указателем), в этом случае запись не производится, функция только возвращает правильное возвращаемое значение.
[править] Утилита printf
В рамках стандарта POSIX описана утилита printf, которая форматирует аргументы по соответствующему шаблону, аналогично функции printf.
Утилита имеет следующий формат вызова: printf format [argument …]
, где
- format — строка формата, по синтаксису похожая на строку формата функции printf.
- argument — список аргументов (0 или более), записанных в строковой форме.
[править] Синтаксис строки форматирования
Строка форматирования представляет из себя последовательность символов, заканчивающаяся символом с кодом 0. Все символы, кроме управляющих последовательностей, копируются в итоговую строку без изменений. Стандартным признаком начала управляющей последовательности является символ % (знак процента, ASCII код 37), для вывода знака '%' используется его удвоение '%%'.
[править] Структура управляющей последовательности
%[флаги][ширина][.точность][размер]тип
Обязательными полями являются символ начала управляющей последовательности (%) и тип.
[править] Флаги
- - (знак минуса; точнее, дефиса, ASCII код 45) — выводимое значение выравнивается по левому краю в пределах минимальной ширины поля (если флаг не указан — по правому)
- + (знак плюса) — всегда указывать знак (плюс или минус) для выводимого десятичного числового значения (если флаг не указан, то знак выводится только для отрицательных чисел)
- (пробел) — помещать перед результатом пробел, если первый символ значения не знак. Символ + имеет больший приоритет, чем пробел. Используется только для десятичных числовых значений.
- # (октоторп) — «альтернативная форма» вывода значения.
- 0 (символ нуля) — дополнять поле символом 0 до ширины, указанной в поле ширина управляющей последовательности. Символ - имеет больший приоритет, чем 0. Используется для типов d, i, o, u, x, X, a, A, e, E, f, F, g, G. Для типов d, i, o, u, x, X, если точность указана, этот флаг игнорируется. Для остальных типов поведение не определено.
[править] Ширина
Ширина (десятичное число или символ звёздочка) указывает минимальную ширину поля (включая знак для чисел). Если представление величины больше, чем ширина поля, то запись выходит за пределы поля (например, %2i для величины 100 даст значение поля в три символа), если представление величины менее указанного числа, то оно будет дополнено, по-умолчанию, пробелами справа, поведение может меняться предшествующими флагами. Если в качестве ширины указана звёздочка, ширина поля указывается в списке аргументов перед значением для вывода (например, printf( "%0*5x", 8, 15 );
выведет текст 0000000f
).
[править] Точность
- указывает на минимальное количество символов, которое должно появиться при обработке типов d, i, o, u, x, X;
- указывает на минимальное количество символов, которое должно появиться после десятичной запятой (точки) при обработке типов a, A, e, E, f, F;
- максимальное количество значащих символов для типов g и G;
- максимальное число символов, которые будут выведены для типа s;
Точность задаётся в виде точки с последующим десятичным числом или звёздочкой (*), если число или звёздочка отсутствует (присутствует только точка), то предполагается, что число равно нулю. Точка для указания точности используется даже в том случае, если при выводе чисел с плавающей запятой выводится запятая.
Если после точки указан символ «звёздочка», то при обработке строки форматирования значение для поля читается из списка аргументов. (При этом, если символ звёздочка и в поле ширины и в поле точности, сначала указывается ширина, потом точность и лишь потом значение для вывода). Например, printf( "%0*.*f", 8, 4, 2.5 );
выведет текст 002.5000
.
[править] Размер
Поле размер позволяет уточнить размер данных, переданных функции. Необходимость в этом поле объясняется особенностями передачи произвольного количества параметров в функцию в языке Си: функция не может «самостоятельно» определить тип и размер переданных данных, так что информация о типе параметров и точном их размере должна передаваться явно. С каждым типом ассоциирован свой размер (например, %i — 4 байта на 32 разрядных системах), а модификатор размера позволяет указать иной размер (например, %hi — 2 байта на 32 разрядных платформах).
Влияние размера на целочисленные типы данных:
размер | целые (d, i, o, u, x, X) | параметр n (см. ниже) |
---|---|---|
hh | signed char или unsigned char | указатель на signed char |
h | unsigned short int или short int | указатель на short int |
l | long int или unsigned long int | указатель на long int |
ll | long long int или unsigned long long int | указатель на long long int |
j | intmax_t или uintmax_t | указатель на intmax_t |
z | size_t (или эквивалентный по размеру знаковый тип) | эквивалентный по размеру size_t знаковый тип |
t | ptrdiff_t (или эквивалентный по размеру знаковый тип) | Эквивалентный по размеру ptrdiff_t знаковый тип |
- Для чисел с плавающей запятой (a, A, e, E, f, F, g, G) есть единственный модификатор: L: он указывает, что аргумент имеет тип long double.
- значение l поля размер для типа «символ» (%c) указывает на размер символа wint_t (многобайтовый символ), для типа «строка» (%s) указывает на многобайтовую строку (тип: *wchar_t)
[править] Тип
Тип указывает не только на тип величины (с точки зрения языка программирования Си), но и на конкретное представление выводимой величины (например, числа могут выводить в десятичном или шестнадцатеричном виде). Записывается в виде одного символа. В отличие от остальных полей является обязательным. Максимальный поддерживаемый по стандартам размер вывода от единичной управляющей последовательности составляет 4095 символов; на практике большинство компиляторов поддерживают существенно большие объёмы данных.
Значения типов:
- d, i — десятичное знаковое число, размер по-умолчанию, sizeof( int ). По-умолчанию записывается с правым выравниванием, знак пишется только для отрицательных чисел;
- o — восьмеричное беззнаковое число, размер по-умолчанию sizeof( int );
- u — десятичное беззнаковое число, размер по-умолчанию sizeof( int );
- x и X — шестнадцатеричное число, x использует маленькие буквы (abcdef), X большие (ABCDEF), размер по-умолчанию sizeof( int );
- f и F — числа с плавающей запятой. По-умолчанию выводятся с точностью 6, если число по модулю меньше единицы, перед десятичной точкой пишется 0. Величины ±∞ представляются в форме [-]inf или [-]infinity, Величина Nan представляется как [-]nan или [-]nan(любой текст далее). Использование F выводит указанные величины заглавными буквами (-INF, NAN). Аргумент по-умолчанию имеет размер double.
- e и E — числа с плавающей запятой в экспоненциальной форме записи (вида 1.1e+44); e выводит символ «e» в нижнем регистре, E — в верхнем (3.14E+0);
- g и G — число с плавающей запятой; форма представления зависит от значения величины (f или e);
- a и A — число с плавающей запятой в шестнадцатеричном виде;
- c — вывод символа с кодом, соответствующим переданному аргументу; переданное число приводится к типу unsigned char (или wint t, если был указан модификатор длины l);
- s — вывод строки с нулевым завершающим байтом; если модификатор длины - l, выводится строка wchar_t*;
- p — вывод указателя, внешний вид может существенно различаться в зависимости от внутреннего представления в компиляторе и платформе (например, 16 битная платформа MS-DOS использует форму записи вида
FFEC:1003
, 32-битная платформа с плоской адресацией использует адрес вида00FA0030
); - n — запись по указателю, переданному в качестве аргумента, количества символов, записанных на момент появления командной последовательности, содержащей n;
- % — символ для вывода знака процента (%), используется для возможности вывода символов процента в строке printf, всегда используется в виде
%%
.
[править] Вывод чисел с плавающей запятой
В зависимости от текущей локали, при выводе чисел с плавающей запятой может использоваться как запятая, так и точка (а, возможно, и другой символ). Поведение printf в отношении разделяющего дробную и целую часть числа символа определяется использующейся локалью (точнее, переменной LC NUMERIC).[14]
[править] XSI расширения в стандарте Single Unix
В рамках стандарта Single UNIX (практически эквивалентного стандарту POSIX), определены следующие дополнения printf по отношению к ISO C, в рамках расширения XSI (X/Open System Interface):
- Добавляется возможность вывода произвольного по номеру параметра (указывается в виде
n$
сразу после символа начала управляющей последовательности, например,printf("%1$d:%2$.*3$d:%4$.*3$d\n", hour, min, precision, sec);
). - Добавлен флаг «'» (одинарная кавычка), который для типов d, i, o, u предписывает разделять классы соответствующим символом.
- тип C, эквивалентный lc ISO C (вывод символа типа wint_t).
- тип S, эквивалентный ls ISO C (вывод строки типа wchar_t*)
- Добавлены коды ошибок EILSEQ, EINVAL, ENOMEM, EOVERFLOW.
[править] Нестандартные расширения
[править] GNU C Library
В рамках GNU C Library (libc) добавлены следующие расширения:
- тип m выводит значение глобальной переменной errno (код ошибки последней функции).
- тип C эквивалентен lc.
- флаг ' (одинарная кавычка) используется для разделения классов при выводе чисел. Формат разделения зависит от LC_NUMERIC
- размер q указывает на тип long long int (на системах, где не поддерживается тип long long int, это то же самое, что и long int
- размер Z является псевдонимом для z, был введён в libc до появления стандарта C99, не рекомендуется к использованию в новом коде.
[править] Регистрация собственных типов
GNU libc поддерживает регистрацию пользовательских типов, позволяя программисту определять формат вывода для собственных структур данных. Для регистрации нового типа используется функция
int register_printf_function (int type, printf_function handler-function, printf_arginfo_function arginfo-function)
, где:
- type — буква для типа (если type = 'Y', то вызов будет выглядеть как '%Y');
- handler-function — указатель на функцию, которая вызывается, printf-функциями, если в строке форматирования встречается тип, указанный в type;
- arginfo-function — указатель на функцию, которая будет вызываться функцией parse_printf_format.
Помимо определения новых типов регистрация позволяет переопределить существующие типы (такие, как s, i).
[править] Microsoft Visual C
В составе Microsoft Visual Studio для языков программирования Cи/Cи++ в формате спецификации printf (и остальных функций семейства) предусмотрены следующие расширения:
- поле размера:
значение поля | тип |
---|---|
I32 | signed __int32, unsigned __int32 |
I64 | signed __int64, unsigned __int64 |
I | ptrdiff_t, size_t |
w | эквивалентно l для строк и символов |
[править] Maple
В среде математических вычислений Maple также имеется функция printf, она имеет следующие особенности:
[править] Форматирование
-
- %a, %A: объект Maple будет выдан в текстовой нотации, это работает для всех объектов (например, матриц, функций, модулей и т. д.). Строчная буква предписывает окружать обратными апострофами символы (имена), которые должны быть окружены ими на входе printf.
- %q, %Q: то же, что и %a/%A, но обрабатываться будет не один аргумент, а все начиная с того, которому соответствует флаг форматирования. Таким образом, флаг %Q/%q может стоять только последним в строке формата.
- %m: форматировать объект в соответствии с его внутренним для maple представлением. Практически используется для записи переменных в файл.
Пример:
> printf("%a = %A", `+`, `+`); `+` = +
> printf("%a = %m", `+`, `+`); `+` = I"+f*6"F$6#%(builtinGF$"$Q"F$F$F$F"%*protectedG
[править] Вывод
Функция fprintf в maple в первом аргументе принимает либо дескриптор файла (возвращаемый функцией fopen), либо имя файла. В последнем случае имя должно имет тип "символ", если имя файла содержит точки, то его обязательно нужно заключить в обратные апострофы или преобразовать функцией convert(имя_файла, symbol).
[править] Уязвимости
Функции семейства printf принимают список аргументов и их размер отдельным параметром (в строке форматирования). Несоответствие строки форматирования и переданных аргументов может приводить к непредсказуемому поведению, повреждению стека и выполнению произвольного кода, приводить к разрушению областей динамической памяти. Многие функции семейства называются «небезопасными» (англ. unsafe), так как не имеют даже теоретической возможности для защиты от некорректных данных.
Так же функции семейства s (без n, такие как sprintf, vsprintf) не имеют ограничителей по максимальному размеру записываемой строки и могут приводить к ошибке переполнения буфера (когда данные записываются за пределы отведённой области памяти).
[править] Поведение при несоответствии строки форматирования и переданных аргументов
В рамках соглашения о вызове функций языка Си, очистку стека осуществляет вызвавшая функция. При вызове printf аргументы (или указатели на них) помещаются в порядке следования записи (слева направо). По мере обработки строки форматирования функция printf читает аргументы со стека. Возможны следующие ситуации:
- количество и тип аргументов совпадают с указанными в строке форматирования (нормальная работа функции)
- в функцию передано больше аргументов, чем указано в строке форматирования (лишние аргументы)
- в функцию передано меньше аргументов, чем требуется согласно строке форматирования (недостаток аргументов)
- в функцию переданы аргументы неправильного размера
- в функцию переданы аргументы правильного размера но неправильного типа
Спецификации языка Си описывают только две ситуации (нормальной работы и лишних аргументов). Все остальные ситуации являются ошибочными и приводят к неопределённому поведению программы (в реальности приводящему к произвольным результатам, вплоть до выполнения незапланированных участков кода).
[править] Избыточное количество аргументов
При передаче излишнего количества аргументов, функция printf читает аргументы, требующиеся для правильной обработки строки форматирования, возвращает управление вызвавшей функции. Вызвавшая функция, в соответствии со спецификацией, очищает стек от параметров, переданных в вызываемую функцию. В этом случае лишние параметры просто не используются, и работа программы продолжается без изменений.
[править] Недостаточное количество аргументов
Если при вызове printf аргументов в стеке меньше, чем требуется для обработки строки форматирования, то недостающие аргументы читаются со стека, несмотря на то, что на стеке находятся произвольные данные (не имеющие отношения к работе printf). Если обработка данных прошла «успешно» (то есть не привела к прекращению работы программы, зависанию или записи в стек), после возврата в вызывающую функцию значение указателя стека возвращается в исходное, и работа программы продолжается.
При обработке «лишних» значений стека, возможны следующие ситуации:
- успешное чтение «лишнего» параметра для вывода (число, указатель, символ и т. д.) — в результаты вывода помещается «почти случайное» значение, прочитанное со стека. Это не представляет из себя опасности для работы программы, но может приводить к компрометации каких-либо данных (вывод значений стека, которые может использовать злоумышленник для анализа работы программы и получению доступа к внутренней/закрытой информации программы).
- ошибка при чтении значения со стека (например, в результате исчерпания доступных значений стека или доступ к «несуществующим» страницам памяти) — такая ошибка вероятнее всего приведёт к аварийному завершению работы программы.
- чтение указателя на параметр. Строки передаются с помощью указателя, при чтении «произвольной» информации со стека, прочитанное (почти случайное) значение используется как указатель на случайную область памяти. Поведение программы в этом случае не определено и зависит от содержимого этой области памяти.
- запись параметра по указателю (
%n
) — в данном случае поведение аналогично ситуации с чтением, но осложняется возможными сторонними эффектами записи в произвольную ячейку памяти.
[править] Несоответствие размеров аргументов
Если размер переданных аргументов не соответствует ожидаемому (согласно строке форматирования), то поведение функции формально не определено. На практике возможны следующие ситуации:
- Передача параметров, размер которых больше ожидаемого (чтение меньшего из большего). При этом на архитектурах с little endian порядком байтов значение будет выведено корректно, на big endian будет выведена бессмысленная информация. При этом последующее значение, даже если его размер соответствует указанному в строке форматирования будет выведено неправильно.
- Передача параметров, размер которых меньше ожидаемого (чтение большего из меньшего). В этом случае возможна ситуация, когда читаются области стека, выходящие за пределы переданных аргументов. Поведение функции в этом случае аналогично поведению в ситуации с недостатком параметров.
[править] Несоответствие типов при совпадении размеров
Если переданные аргументы совпадают по размеру, но имеют отличный тип, то в большинстве случаев работа программы будет «почти правильной» (не вызовет ошибок доступа к памяти), хотя выводимое значение вероятнее всего будет бессмысленным (например, указание %hhi вместо %c даст правильный размер, но приведёт к выводу числа вместо символа). В случае ошибки между знаковым и беззнаковым типом ошибка будет "скрытой" (будет проявляться только на больших беззнаковых и отрицательных числах). В случае ошибки между числовым и строковым типом (вывод строки вместо числа) возможны ошибки доступа к памяти.
[править] Выравнивание значений на стеке
Во многих компиляторах при передаче значений через стек значение выравнивается до размера машинного слова, в этом случае незначительные ошибки в указании размеров (особенно в little-endian архитектуре) приводят к правильному поведению программы, несмотря на неправильные размеры параметров. Например, строка printf("%i=%i", 'a', 'b')
отработает правильно на большинстве архитектур (не смотря на то, что размер %i - int (16, 32 или 64 бита), а размер 'a' (char) - 8 бит)
[править] Уязвимость строки форматирования
Так как printf (и остальные функции семейства) могут выводить текст строки форматирования без изменений, если он не содержит управляющих последовательностей, то возможен вывод текста командой
printf(text_to_print);
В случае, если text_to_print получается из внешних источников (читается из файла, получается от пользователя или операционной системы), то наличие в получаемой строке знака процента может приводить к крайне нежелательным последствиям (вплоть до зависания программы).
Пример некорректного кода:
printf(" Current status: 99 % stored.");
В этом примере содержится управляющая последовательность «% s», содержащая признак управляющей последовательности (%), флаг (пробел) и тип данных «строка» (s). Функция, приняв управляющую последовательность, попытается прочитать из стека указатель на строку. Так как функции не передавались дополнительные параметры, значение, которое будет прочитано со стека, не определено. Полученное значение будет интерпретировано как указатель на строку с завершающим нулём. Вывод такой «строки» может привести к выводу произвольного дампа памяти, ошибке доступа к памяти и разрушению стека. Такой тип уязвимости называют атакой на строку форматирования (англ. Format string attack).[15]
[править] Переполнение буфера
Функция printf при выводе результата не ограничивается по максимальному количеству выводимых символов. Если в результате ошибки или недосмотра будет выведено больше символов, чем ожидалось, худшее, что может случиться — это «разрушение» картинки на экране. Созданная по аналогии с printf функция sprintf также не ограничивалась в максимальном размере результирующей строки. Однако в отличие от «бесконечного» терминала, память, которую выделяет приложение для результирующей строки, всегда ограничена. И в случае выхода за ожидаемые рамки, запись производится в области памяти, принадлежащие другим структурам данных (или, вообще, в недоступные участки памяти, что практически на всех платформах означает аварийное завершение программы). Запись в произвольные области памяти приводит к непредсказуемым эффектам (которые, возможно, проявятся много позже и не в форме ошибки программы, а в форме повреждения пользовательских данных). Отсутствие ограничения на максимальный размер строки является принципиальной ошибкой планирования при разработке функции. Именно из-за этого функции sprintf и vsprintf имеют статус небезопасных. Взамен им были разработаны функции snprintf, vsnprintf, принимающие дополнительный аргумент, ограничивающий максимальную результирующую строку. Появившаяся много позже функция swprintf (для работы с многобайтными кодировками) учитывает эту недоработку и принимает аргумент для ограничения результирующей строки. (Именно поэтому нет функции snwprintf).
Пример опасного вызова sprintf:
char buffer[65536]; char* name = get_user_name_from_keyboard(); sprintf(buffer, "User name:%s", name);
В вышеприведённом коде неявно предполагается, что пользователь не будет печатать 65 тысяч символов на клавиатуре, и буфера «должно хватить». Но пользователь может перенаправить ввод с другой программы или всё-таки ввести более 65 тысяч символов. В этом случае произойдёт повреждение областей памяти и поведение программы станет непредсказуемым.
[править] Сложности в использовании
Функции семейства printf используют типы данных языка Си, размер типов, и соотношение размеров типов может меняться от платформы к платформе (например, на 64-битных платформах в зависимости от выбранной модели (LP64, LLP64 или ILP64) размеры типов int и long могут различаться). При этом "правильно" работавший код на одной платформе начинает выдавать неправильный результат на другой (в ряде случаев, возможно, приводя к повреждению данных).
Например, код printf( "text address: 0x%X", "text line" );
работает правильно на 32-битной платформе (размер ptrdiff_t и размер int 32 бита) и на 64-битной модели IPL64 (где размеры ptrdiff_t и int 64 бита), но даст неверный результат на 64-битной платформе модели LP64 или LLP64, где размер ptrdiff_t 64 бита, а размер int 32 бита.[16]
[править] Примеры реализации
[править] Ссылки
- ↑ Краткое описание языка BCPL
- ↑ Руководство по языку Би
- ↑ Описание функции sprintf в документации Perl
- ↑ Описание форматирующего оператора для строковых типов в Питоне
- ↑ Описание функции printf в составе PHP
- ↑ Описание функции java.io.PrintStream.printf() в Java 1.5
- ↑ Описание функции printf в документации Ruby
- ↑ Описание функции format в документации TCL
- ↑ Описание шаблона строки для printf в документации GNU Octave
- ↑ Описание printf в документации к Maple[источник?]
- ↑ R. Fourer, D.M. Gay, and B.W. Kernighan. AMPL: A Modeling Language for Mathematical Programming, 2nd Ed.. Pacific Grove, CA: Brooks/Cole--Thomson Learning, 2003.
- ↑ GNU Emacs Lisp Reference Manual, Formatting Strings
- ↑ Описание модуля Printf в документации OCaml
- ↑ §7.11.1.1 ISO/IEC 9899:TC2, LC_NUMERIC определяет, в частности, форму представления разделителя дробной и целой части.
- ↑ Описание уязвимостей printf, Robert C. Seacord: Secure Coding in C and C++. Addison Wesley, September, 2005. ISBN 0-321-33572-4
- ↑ Описание проблем переноса приложений с 32 на 64 битную архитектуру. [1]
[править] Источники
- printf, fprintf, snprintf, vfprintf, vprintf, vsnprintf, vsprintf в стандарте ISO/IEC 9899:TC2 (ISO C) [4]
- printf, fprintf, sprintf, snprintf в стандарте Single Unix [5]
- vprintf, vfprintf, vsprintf, vsnprintf в стандарте POSIX [6]
- wprintf, swprintf, wprintf в стандарте POSIX [7]
- vfwprintf, vswprintf, vwprintf в стандарте POSIX [8]
- wsprintf в MSDN [9]
- wvnsprintf в MSDN [10]
- wnsprintf в MSDN [11]
- wvsprintf в MSDN [12]
- wnsprintf в MSDN [13]
- asprintf, vasprintf в man-pages в Linux [14], в документации к libc [15]
- Описание синтаксиса строки форматирования в руководстве libc [16].
- Описание строки форматирования в документации к Microsoft Visual Studio 2005 [17]
- Описание register_printf_function [18], [19]
[править] См также
- Си
- scanf
- Стандартная библиотека
- POSIX
- cout
Эта статья входит в число хороших статей русского раздела Википедии. |