名称
printf, fprintf, dprintf, sprintf, snprintf, vprintf, vfprintf, vdprintf, vsprintf, vsnprintf –
格式化输出转换
概要
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int dprintf(int fd, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vdprintf(int fd, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
对于glibc来说要有功能测试宏才能使用(参考feature_test_macros):
snprintf(), vsnprintf():
_BSD_SOURCE || _XOPEN_SOURCE >= 500 || _ISOC99_SOURCE ||
_POSIX_C_SOURCE >= 200112L;
或者 cc -std=c99
dprintf(), vdprintf():
从glibc 2.10开始:
_XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
在glibc 2.10之前:
_GNU_SOURCE
说明
printf函数族按照下文描述的格式产生输出。printf和vprintf把输出写到stdout,
即标准输出流。fprintf和vfprintf把输出写到stream指向的流。sprintf,snprintf,
vsprintf和vsnprintf把输出写到参数str指向的字符串。
dprintf等价于fprintf,除了它输出到文件描述符fd而不是标准I/O流之外。
snprintf和vsnprintf最多写size个字节(包括结束符(’\0’))到str。
vprintf,vfprintf,vdprintf,vsprintf,vsnprintf分别等价于printf,fprintf,dprintf,
sprintf,snprintf,除了它们有一个va_list类型的参数而不是可变数量的参数之外。
这些函数并不调用va_end宏。因为它们调用了va_arg宏,调用后ap的值是未定
义。参考stdarg。
所有的这些函数按照参数format的控制来写输出,format指定了后续的参数(或
者通过stdarg系列工具获取到的可变数量的参数)在输出的过程中怎样做转换。
C99和POSIX.1-2001规定,如果调用sprintf,snprintf,vsprintf或者vsnprintf引
起了对象之间重叠的部分发生了拷贝,那么结果是未定义的(例如,目标字符串数
组和一个输入参数指向了相同的缓冲区)。阅读注意。
格式串的格式
格式串是一个字符串,以它的初始移位状态开始和结束,如果有的话。格式串由零
个或多个指令组成:普通字符(不是’%’)原封不动拷贝到输出流,而每条转换规则
会引发获取后续零个或者多个参数的动作。转换规则由字符’%’开头,由转换说明符
结束,在它们之间可能会有(按序排列)零个或多个标识符,一个可选的最小字段
宽度,一个可选的精度值,一个可选的长度修饰符。
参数必须与转换说明符正确对应(在类型提升之后)。默认情况下,按照出现的顺
序处理参数,每一个’*’和每一个转换说明符都引发取下一个参数(如果给出的参数
不够多,就会出错)的动作。在处理参数的时候也可以明确指出要处理哪一个参数,
这通过用"%m$"代替'%'和"*m$"代替'*',十进制整数m表示被处理的参数在参数列
表中的位置,从1开始算起。所以,
printf("%*d", width, num);
和
printf("%2$*1$d", width, num);
是等价的。'$'风格可以重复取到相同的参数。SUS支持'$'风格,C99不
支持。如果使用了'$'风格的格式,那么同一语句中所有的参数转换都要使用这种格
式,这时可以和"%%"一起使用,因为"%%"并不会引发取参数的动作。使用'$'风格时,
参数转换不能有间断,比如参数1和参数3做了格式化转换,那么在格式串中也需
要对参数2做格式转换。
一些数字转换会用到基数字符(小数点)和千位分组字符,实际使用哪个字符是由
本地化语境LC_NUMERIC宏决定的。POSIX本地化语境使用'.'作为基数字符并
且没有千位分组字符。所以,
printf("%'.2f", 1234567.89);
在POSIX本地化语境中打印"1234567.89",在nl_NL本地化语境中打印
"1234567,89",在da_DK本地化语境中打印"1.234.567,89"。
标识符
字符’%’后面可以跟零个或多个标识符:
# 输出值被转换成一种“替代形式”,对于转换说明符为’o’来说,输出字符串
的第一个字符是’0’(如果本来不是’0’,那就在前面加’0’)。对于x和X的
转换,一个非零的结果会有一个"0x"(X转换就是"0X")串在最前面。对于
a,A,e,E,f,F,g和G的转换,结果中总是会包含一个小数点,尽管
小数点后面没有数字(一般情况下,只有在小数点后面有数字的时候小数
点才会出现在结果中)。对于g和G的转换,尾部的零会被删除。对于其
他的转换,结果是未定义的。
0 输出值补0,对于d,i,o,u,x,X,a,A,e,E,f,F,g和G 的转
换,会在输出值的左边补0而不是空格。如果0和-标识同时出现,那么0
标识被忽略。如果在做数字转换(d,i,o,u,x和X)的时候给出了精确
度,那么0标识被忽略。对于其他的转换,结果是未定义的。
- 输出值将在字段边界上进行左对齐。(默认是右对齐的。)会在输出值的
右边补空格而不是在左边补空格或者0。当同时出现时,-覆盖0。
' ' (一个空格)当有符号的转换产生一个正数(或者空串)时,正数的左边
会有一个空格。
+ 做有符号的转换时一个符号(加号+或者减号-)总是会出现在数字的前面。
默认情况下,只有是负数的时候才会出现符号。当同时出现时,+覆盖' '。
上述5个字符由C99定义,SUS在此之上增加了一个标识符。
' 对于十进制转换(i,d,u,f,F,g,G),如果有本地化语境的需要,那
么输出被组织成千位分组的形式。注意,很多版本的gcc不能解析这个选
项并且会产生一个警告。(SUSv2不支持%'F, 但是SUSv3支持它。)
glibc 2.2在以上基础之上又增加了一个标识符。
I 对于十进制转换(i,d,u),如果有本地化语境的需要,那么在输出中使
用本地化语境输出数字。例如,从glibc 2.2.3开始,在波斯语境中使用阿
拉伯-印度数字。
字段宽度
一个可选的十进制数字串(第一个数字不为0)指定最小字段宽度。如果输出值的
字符数比字段宽度小,那么在左边填充空格(或者在右边,如果使用了左对齐标示)。
也可以使用"*"或者"*m$"(m是一个十进制整数)的方式表示字段宽度由下一个参
数或者第m个参数确定,该参数必须为int类型。一个负数的字段宽度会被当做是
一个'-'标识后面跟了一个正数的字段宽度。字段宽度不存在或者小的字段宽度在任
何情况下都不会导致一个字段被截断。如果转换结果的宽度比字段宽度要宽,那该
字段就被扩展以包含整个转换结果。
精度值
一个可选的精度值,以小数点后面跟着一串十进制数字串的形式出现,也可以使用
"*"或者"*m$"(m是一个十进制整数)的方式表示精度值由下一个参数或者第m个
参数确定,该参数必须为int类型。如果精度只给出'.',那么精度值会被当做是0。
一个负数的精度被当做是省略精度值。该值给出了d、i、o、u、x和X转换出现的
最小位数,a、A、e、E、f和F转换的基数字符后出现的位数,g和G转换的最大
有效数字数,或s和S转换从字符串打印的最大字符数。
长度修饰符
在这部分内容里,“整数转换”代表d,i,o,u,x或X的转换。
hh 后面跟一个“整数转换”对应一个signed char或者unsigned char参数,或
者跟一个n的转换对应一个指向signed char参数的指针。
h 后面跟一个“整数转换”对应一个short int或者unsigned short int参数,或
者跟一个n的转换对应一个指向short int参数的指针。
l 后面跟一个“整数转换”对应一个long int或者unsigned long int参数,或者
跟一个n的转换对应一个指向long int参数的指针,或者跟一个c的转换
对应一个wint_t参数,或者跟一个s的转换对应一个指向wchar_t参数的
指针。
ll 后面跟一个“整数转换”对应一个long long int或者unsigned long long int
或者跟一个n的转换对应一个指向signed char参数的指针。
L 后面跟a,A,e,E,f,F,g或者G的转换对应long double参数。(C99
支持%LF,但是SUSv2不支持。)这是ll的同义词。
j 后面跟一个“整数转换”对应一个intmax_t或者uintmax_t参数,或者跟一个
n的转换对应一个指向intmax_t参数的指针。
z 后面跟一个“整数转换”对应一个size_t或者ssize_t参数,或者跟一个n的
转换对应一个指向size_t参数的指针。
t 后面跟一个“整数转换”对应一个ptrdiff_t参数,或者跟一个n的转换对应一
个指向ptrdiff_t参数的指针。
SUSv3支持上述所有的长度修饰符,SUSv2只支持h(包括hd,hi,ho,hx,hX,
hn)、l(包括ld,li,lo,lx,lX,ln,lc,ls)和L(包括Le,LE,Lf,Lg,LG)。
转换说明符
转换说明符指定转换的类型,转换说明符及其含义如下:
d, i int类型参数转换为有符号十进制。如果指定了精度值,那么精度值表示
结果中必须至少出现的数字的个数,如果转换的值的数字个数小于精度值,
那么在左边补0。默认精度值为1。如果转换0时指定精度值为0,那么输
出为空。
o, u, x, X
unsigned int类型参数转换为无符号八进制(o),无符号十进制(u),
或者无符号十六进制(x和X)。字符abcdef用于x的转换,字符ABCDEF
用于X的转换。如果指定了精度值,那么精度值表示结果中必须至少出现
的数字的个数,如果转换的值的数字个数小于精度值,那么在左边补0。
默认精度值为1。如果转换0时指定精度值为0,那么输出为空。
e, E double类型参数四舍五入并转换成[-]d.ddde±dd的格式,小数点前面有一
个数字,小数点后面数字的个数等于精度值。如果没有指定精度值,那么
默认为6。如果精度值为0,那么不会出现小数点。E的转换在指数中使用
E(而不是e)。指数总是至少包含两个数字。如果值是0,那么指数为00。
f, F double类型参数四舍五入并转换成[-]ddd.ddd的格式,小数点后面数字的
个数等于精度值。如果没有指定精度值,那么默认为6。如果精度值为0,
那么不会出现小数点。如果出现了小数点,那么至少会有一个数字出现在
小数点的前面。
(SUSv2不支持F,并指明可以提供字符串来代表无穷大和NaN。SUSv3
支持了F。C99在f转换中使用"[-]inf"或者"[-]infinity"代表无穷大,"nan"
代表NaN,在F转换中使用"[-]INF"或者"[-]INFINITY"或者"NAN*"。)
g, G double类型参数以f或者e的方式转换(G则对应F或者E)。精度值表
示有效数字的个数。如果没有指定精度值,那么默认为6。如果精度值为0,
那么它被当成1。如果转换后的指数小于-4或大于等于精度值,则使用e
的方式转换。从结果的小数部分删除尾部的0。只有小数点后面至少有一
个数字时,小数点才会出现。
a, A (C99支持,SUSv2不支持,SUSv3支持)double类型参数转换成
[-]0xh.hhhhp±的十六进制形式。A转换使用了前缀0X,字符ABCDEF和指
数分隔符P。小数点之前会有一个十六进制的数字,小数点之后数字的个
数等于精度值。如果以2为底能足够精确表示一个值,那么就用默认精度
值,否则精度值就会很大并以此区分double类型的值。小数点前的数字对
于非规范化数字是未指定的,对于规范化数字是非零但未指定的。
c 如果没有指定l修饰符,那么int类型参数转换为unsigned char类型并写出
对应的字符。如果指定l修饰符,通过调用wcrtomb()函数将wint_t(宽字
符)类型参数转换为多字节序列,转换状态从初始状态开始,并写入生成的
多字节字符串。
s 如果没有指定l修饰符,那么const char *参数被解释成指向字符数组的指针
(指向字符串),数组中的字符被写入,直至(但不包括)结束符('\0')。如
果指定了精度值,那么写出的字符数不会超过精度值。如果指定了精度值,
那么数组可以没有结束符。如果没有指定精度值或者指定的精度值大于数组
的大小,那么这个数组必须要包含一个结束符。
C (C99和C11不支持,SUSv2、SUSv3、SUSv4支持。)lc的同义词。不要
使用它。
S (C99和C11不支持,SUSv2、SUSv3、SUSv4支持。)ls的同义词。不要
使用它。
p 以十六进制方式打印void *指针参数(就好像使用%#x或者%#lx)。
n 到目前为止写入的字符数存储到相应参数所指向的整数中,这个参数应该是
一个int *,或者是一个变体,这个变体的大小能够匹配(可选)长度修饰符。
不做转换参数的动作。如果转换规则中包含了标识符、字段宽度或者精度
值,那么结果是未定义的。
m (Glibc的扩展。)打印strerror(errno)的输出,不需要参数。
% 写出一个'%'。没有参数被转换。完整的转换规则是'%%'。
返回值
成功时,这些函数返回打印的字符的数量(不包括用于结束输出字符串的结束符)。
函数snprintf和vsnprintf写不超过size个字节(包括结束符(‘\0’))。如果输出
因为该限制而被截断,那么返回值为空间足够时最终应该输出的字符数量(不包括
结束符)。因此,返回值为size或者比size更大意味着输出被截断。(也可以阅
读注意。)
如果出错,则返回一个负数。
线程安全特征
接口 | 特征 | 值 |
printf(), fprintf(), sprintf(), snprintf(),vprintf(), vfprintf(), vsprintf(), vsnprintf() | 线程安全 | locale级别多线程安全 |
标准
函数fprintf(), printf(), sprintf(), vprintf(), vfprintf(), vsprintf()遵循POSIX.1-2001,
POSIX.1-2008, C89, C99。
函数snprintf(), vsnprintf()遵循POSIX.1-2001, POSIX.1-2008, C99。
函数dprintf()和vdprintf()是GNU的扩展并且将在POSIX.1-2008标准化。
SUSv2和C99对于函数snprintf的返回值相互矛盾,当参数size等于0时,SUSv2
规定返回一个小于1的未定义值,在这种情况下C99允许str为NULL,C99(总是)
返回空间足够时最终应该输出的字符数量。POSIX.1-2001及其后续规范以C99为
准。
glibc 2.1增加了长度修饰符hh、j、t和z,转换说明符a和A。
glibc 2.2增加了符合C99语法的转换说明符F,标识符I。
注意
一些程序轻率地使用如下代码追加文本到buf,
sprintf(buf, "%s some further text", buf);
然而,标准明确说明当调用sprintf,snprintf,vsprintf和vsnprintf时,如果源缓冲
区和目的缓冲区有重叠,那么结果是未定义的。取决于gcc的版本和使用的编译器
选项,调用上述类似代码将不会产生预期的结果。
从glibc 2.1起,glibc以遵循C99的方式实现了函数snprintf和vsnprintf,使得函
数的表现跟上面描述的一样。glibc 2.0.6及其之前的版本,当输出被截断时它们将
输出-1。
BUGS
因为函数sprintf和vsprintf接收任意长度的字符串,调用者必须小心不要溢出实际
空间,这通常无法保证。请注意,生成的字符串长度取决于语言环境且难以预测。
使用snprintf和vsnprintf代替(或asprintf 和 vasprintf)。
形如printf(foo);的代码通常会导致bug,因为foo可能包含一个%字符,如果foo
来自于一个不受信任的用户的输入,它可能包含%n,导致printf() 调用写入内存并
产生一个安全漏洞。
示例
打印Pi到小数点的后5位:
#include <math.h>
#include <stdio.h>
fprintf(stdout, "pi = %.5f\n", 4 * atan(1.0));
以"Sunday, July 3, 10:02"的形式打印日期和时间,weekday和month是指向字符串
的指针:
#include <stdio.h>
fprintf(stdout, "%s, %s %d, %.2d:%.2d\n",
weekday, month, day, hour, min);
许多国家使用日-月-年的顺序。因此一个国际化版本必须能按照参数format指定
的顺序打印参数:
#include <stdio.h>
fprintf(stdout, format,
weekday, month, day, hour, min);
format由语言环境决定,并且可能改变参数的排列。format为如下值:
"%1$s, %3$d. %2$s, %4$d:%5$.2d\n"
可能获得"Sonntag, 3. Juli, 10:02"。
分配一个足够大的串把它包含进去(glibc 2.0和glibc 2.1都适用):
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
char *
make_message(const char *fmt, ...)
{
int size = 0;
char *p = NULL;
va_list ap;
/* 确定所需要的的大小 */
va_start(ap, fmt);
size = vsnprintf(p, size, fmt, ap);
va_end(ap);
if (size < 0)
return NULL;
size++; /* 保存'\0' */
p = malloc(size);
if (p == NULL)
return NULL;
va_start(ap, fmt);
size = vsnprintf(p, size, fmt, ap);
if (size < 0) {
free(p);
return NULL;
}
va_end(ap);
return p;
}
如果在glibc 2.0.6之前的版本中出现了截断,会被当成一个错误而不是被优雅地处
理。
推荐阅读
printf,asprintf,dprintf,puts,scanf,setlocale,wcrtomb,wprintf,locale
版本记录
这个页面是Linux man-pages项目4.04版本的一部分。关于该项目的信息和bug
报道可以在该网站找到:http://www.kernel.org/doc/man-pages/。
2015-08-08