在C语言程序设计中,printf是最基础也最容易误用的标准库函数之一。它属于

格式控制符的基本分类与匹配规则
printf的第一个参数是字符串常量,其中普通字符原样输出,以百分号%开头的称为转换说明(格式控制符)。每个控制符对应后续一个可变参数,编译器不会在编译期严格检查类型是否匹配,这导致很多初学者写出printf("%d", 3.14);这类代码,运行时得到无意义整数。
常用的格式符包括:%d或%i用于有符号十进制整型(int),%u对应无符号整型,%ld对应long,%lld对应long long;%f用于浮点(注意float在可变参数中会自动提升为double,所以用%f即可);%c输出字符,%s输出以空字符结尾的字符串,%p输出指针地址,%x或%X输出十六进制。长度修饰符如h、l、ll、L必须和基字符配合使用,否则行为未定义。
下面示例展示了正确与错误的用法对比。错误示例中用%d打印long类型,在64位Linux上可能只读到低32位,高32位被当作下一个参数,造成输出错乱。
#include <stdio.h>
int main() {
long big = 1234567890123L;
// 错误:用%d打印long
printf("wrong: %dn", big);
// 正确:用%ld打印long
printf("right: %ldn", big);
float f = 1.2f;
// float可变参数提升为double,用%f正确
printf("float: %fn", f);
return 0;
}
精度、宽度与标志位的组合技巧
除了类型匹配,格式控制符还支持%[flags][width][.precision][length]specifier的完整结构。宽度指定最小输出字符数,不足时默认右对齐空格填充;精度对整数表示最小数字位数(前补零),对%f表示小数点后位数,对%s表示最大输出字符数。标志位-左对齐,0用零填充,+显示正负号,空格在正数前加空格。
实际开发中,打印浮点保留两位小数的需求极常见:printf("%.2f", 3.14159);输出3.14。若希望总宽10且左对齐,可写%-10.2f。对字符串截断防溢出也很有用,例如printf("%.5s", "hello world");只输出hello。但需注意精度对%s不会自动在目标缓冲区加结束符,仅限制读取源长度。
下表列出常见组合效果:
| 格式串 | 参数 | 输出示例 |
|---|---|---|
| %5d | 42 | 42 |
| %05d | 42 | 00042 |
| %-5d | 42 | 42 |
| %.3f | 2.71828 | 2.718 |
在构造动态SQL日志或报表时,合理使用宽度与精度可令列对齐美观。但应避免在格式串中拼接不可信用户输入,否则若用户输入含%n可能触发写入漏洞(虽现代libc已默认禁用%n于非字面量格式串)。
可变参数机制与安全性注意事项
printf的声明为int printf(const char *format, ...);,省略号表示可变参数。C语言通过va_list宏在运行时按顺序取参,函数本身不知道实参个数和类型,完全依赖格式串中的%数量。若格式串中%多于实参,会读取栈上随机数据;若少于实参,多余参数被忽略。这也是为什么printf(str)当str含%时极其危险。
为提升安全性,应始终使用字面量格式串,或将用户输入作为%s参数:printf("%s", user_input);。在需检查格式串与参数匹配的场合,可开启编译器警告(如gcc的-Wformat),它能在编译期捕获大部分类型不匹配。对于性能敏感且格式固定的场景,可考虑fprintf到缓冲区再用fwrite,减少锁开销。
另一个易错点是%p与%x打印指针的差异:%p自带0x前缀且适配平台地址宽度,而%x将指针截断为unsigned int在64位下丢失高位。以下代码演示了安全打印指针的方式:
#include <stdio.h>
int main() {
int val = 10;
int *p = &val;
// 正确打印指针
printf("pointer: %pn", (void *)p);
// 错误:用%x打印64位指针
printf("bad hex: %xn", (unsigned int)p);
return 0;
}
综上,掌握printf的用法不仅是记忆格式符,更要理解其弱类型可变参数的设计哲学。在跨平台开发中,显式使用长度修饰符、开启编译警告、杜绝格式串注入,才能写出健壮的输出逻辑。