一、问题引入
在项目实践中,时常发生自定义的格式化输入输出函数中,因为打印格式与参数类型不匹配导致的故障。典型的问题有:
- 格式化输出使用%s打印一个整型,导致进程跑飞
- 格式化输出使用%d打印一个长整型,导致打印数据截断
- 格式化输出格式符多于实际参数个数,导致进程跑飞
- 格式化输入使用%u打印一个 unsigned char 或 unsigned short 类型整型,导致踩内存
下面,借助 GCC 编译器的__attribute__((format(...))) 扩展属性,用于在编译时进行格式字符串的静态检查,提前发现问题。
示例
cpp
int ios_snprintf_s(char * restrict s, rsize_t n, const char *restrict format, ...) __attribute__((format(printf, 3, 4)))
二、示例详解
1. 格式字符串检查
- format: 指定这是一个格式字符串函数
- printf: 指定使用 printf 系列的格式规则进行检查
- 3, 4: 指定参数位置
- 第3个参数是格式字符串位置 (即const char *restrict format)
- 第4个参数是可变参数列表的开始位置 (...)
2. 编译时检查
编译器会在编译时检查:
cpp
// 示例用法
char buf[100];
ios_snprintf_s(buf, sizeof(buf), "Name: %s, Age: %d", name, age);
// 如果格式字符串与参数不匹配,编译时会报警告
ios_snprintf_s(buf, sizeof(buf), "Name: %s, Age: %d", name);
// 警告:参数数量不足
- 实际效果
cpp
// 正确的使用 - 无警告
ios_snprintf_s(buffer, size, "Hello %s!", name);
// 错误的使用 - 编译警告
ios_snprintf_s(buffer, size, "Hello %s!", 123);
// 警告:格式 '%s' 需要 'char*' 但参数是 'int'
ios_snprintf_s(buffer, size, "Value: %d");
// 警告:格式 '%d' 缺少对应参数
三、类似属性
|------------------------|-----------------|
| 属性 | 用途 |
| format(printf, n, m) | printf 风格格式检查 |
| format(scanf, n, m) | scanf 风格格式检查 |
| format(strftime, n, m) | strftime 风格格式检查 |
这种属性在项目安全编码中特别有用,可以提前发现格式化字符串相关的潜在错误。