vfprintf
vfprintf是C标准库中用于格式化输出的一组函数之一,它把已由stdarg.h机制收集成va_list类型的参数列表,按格式字符串转换后写入指定的文件流。与常见的fprintf相比,两者的转换规则完全一致,区别在于vfprintf不接收可变数量的实参,而是接收一个参数列表对象,因此可以被转发给其他可变参数函数使用。该函数自C89/ISO C90起即被纳入标准,并被POSIX.1-2001、POSIX.1-2008、C99等规范采纳,是日志、错误处理等封装接口的常用基础函数。
定义
vfprintf是C标准库中声明于stdio.h、并依赖stdarg.h头文件的一类格式化输出函数,其原型为 int vfprintf(FILE *restrict stream, const char *restrict format, va_list ap)[1]。它把由ap指向的参数列表中的各个实参,按format指向的格式字符串进行转换,再把结果写入stream所指定的文件流[2]。
按照技术文档的表述,该函数的实际行为与fprintf相同,唯一的区别是调用方式从数量可变的实参改为传入一个由stdarg.h定义的参数列表[3]。同一族函数还包括写入标准输出的vprintf、写入字符数组的vsprintf,以及在C99中补充进来的vsnprintf[4]。
原理
可变参数函数在被调用时无法直接把自己收到的实参原样交给另一个可变参数函数,标准提供的办法是先用va_start宏取得va_list对象,再把这个对象传给v函数族[2]。vfprintf内部通过va_arg宏逐个取出参数,因此它本身不会调用va_end,返回后ap的取值处于不确定状态,调用方需要在用完后自行调用va_end清理[1]。
格式字符串由普通字符和转换规范组成,普通字符原样复制到输出流,每个转换规范以百分号开头,可带标志、字段宽度、精度和长度修饰符,末尾是转换说明符[5]。函数的返回值是实际写出的字符个数,不包括结尾的空字符;一旦发生输出错误,则返回负值[6]。
常见的封装写法是先声明一个va_list变量,用va_start绑定到最后一个具名参数,然后把它交给vfprintf输出,最后调用va_end结束访问[7]。
发展历程
vfprintf在C89/C90标准中即已出现,其条款编号为4.9.6.7;到C99标准时,对应条款调整为7.19.6.8,C11标准中该函数位于7.21.6.8,C23标准继续保留这一函数[5]。这一族函数在POSIX体系中也有对应条目,POSIX.1-2001与POSIX.1-2008均收录vfprintf,并与ISO C标准保持一致[4]。
按POSIX的修订记录,vprint系列最初随Issue 1发布,源自System V接口定义;Issue 5加入vsnprintf,Issue 6则把vfprintf、vprintf、vsnprintf和vsprintf按ISO/IEC 9899:1999的要求做了更新[8]。在实现层面,glibc 2.1增添了hh、j、t、z等长度修饰符以及a、A转换,glibc 2.2补充了F转换和I标志[9]。
C11标准以Annex K的形式给出边界检查接口,其中包括vfprintf_s,它与vfprintf原型相同,差别是不支持%n转换说明符,并把空指针或非法格式字符串等情形定义为可诊断的运行时约束违反[10]。这类函数属于可选特性,部分实现对它的支持并不完整[10]。
应用
最典型的用法是编写带格式化参数的自定义接口,例如把错误信息统一送往stderr的error函数:函数本身接受可变数量的实参,内部把参数列表转交给vfprintf完成实际输出[2]。系统日志接口vsyslog采取同样的思路,它相对syslog的差别就在于以stdarg.h定义的参数列表代替可变数量的实参[11]。
在库的内部实现上,printf、fprintf、sprintf等可变参数函数常被写成对相应v函数的薄封装,把主要格式化逻辑集中在v函数中,从而避免同一套解析代码被重复实现[12]。
局限
vfprintf本身不检查格式字符串与实参是否匹配,当转换说明符多于实际提供的参数时会继续从栈上读取数据,行为是未定义的[13]。如果格式字符串的内容可以被外部输入控制,就可能被利用来读写任意内存,因此安全编码规范要求不要把用户可控的字符串当作格式字符串使用[14]。其中%n会把自己之前已写出的字符数写入对应指针所指的位置,属于较常被利用的转换说明符[15]。
该函数只负责向文件流写出内容,不能限制写出长度,写入字符数组的sprintf和vsprintf会假定目标空间足够长,调用方必须自行保证不发生溢出[4]。Annex K中的vfprintf_s虽然加入了指针与格式串检查,但只能发现空指针一类问题,无法核对实参类型是否与转换说明符相符,这类检查仍依赖编译器的告警[12]。
另外,按标准的规定,当sprintf、snprintf、vsprintf、vsnprintf的源与目标缓冲区出现重叠时,结果同样是未定义的[4]。
参见
参考资料
- vfprintf [引用日期2026-10-14]
- vfprintf - man pages section 3: Basic Library Functions [引用日期2026-10-14]
- vprintf [引用日期2026-10-14]
- vsnprintf.3.en [引用日期2026-10-14]
- vfprintf [引用日期2026-10-14]
- vfprintf, vfwprintf [引用日期2026-10-14]
- man pages section 3: Basic Library Functions [引用日期2026-10-14]
- vfprintf [引用日期2026-10-14]
- vfprintf.3.en [引用日期2026-10-14]
- Security TR Editor's Report [引用日期2026-10-14]
- va_end(ap); [引用日期2026-10-14]
- stackoverflow.com 上的网页 [引用日期2026-10-14]
- C/C++ セキュアコーディング [引用日期2026-10-14]
- viewpage [引用日期2026-10-14]
- c/io/vfprintf: Difference between revisions [引用日期2026-10-14]