snprintf的size参数应填整个缓冲区字节数(如sizeof(buf)),而非剩余空间;需检查返回值判断是否截断或出错,并确保源字符串以\0结尾或用%.*s限制长度。

snprintf 的 size 参数到底填什么
填的是整个目标缓冲区的字节数,不是剩余空间。比如 char buf[1024],调用时必须传 sizeof(buf) 或硬写 1024,不能传 1024 - strlen(buf) —— 后者是常见误用,会导致重复计算已写入长度,实际可用空间被低估,反而可能截断内容。
为什么 snprintf 比 sprintf 安全,但仍有坑
snprintf 会检查 size 并确保结尾有 \0,即使内容被截断。但它不自动帮你判断“是否真的截断了”。你需要检查返回值:
- 返回值
< 0:编码错误(如宽字符转换失败) - 返回值
>= size:内容被截断,目标缓冲区只写了size - 1个字符 +\0 - 返回值在
[0, size-1]范围内:写入成功,且未截断
漏掉返回值检查,就等于没防溢出。例如:
int n = snprintf(buf, sizeof(buf), "%s:%d", host, port);后面不判断
n < 0 || n >= (int)sizeof(buf),就无法知道是否出问题。
字符数组声明大小和 snprintf 的协同逻辑
数组大小必须显式预留至少 1 字节给终止符 \0。如果业务上最大需要存 255 个可见字符,就得声明 char buf[256],然后传 sizeof(buf) 给 snprintf。否则哪怕只差 1 字节,snprintf 也会把最后一个字符换成 \0,导致内容丢失。
立即学习“C++免费学习笔记(深入)”;
另外注意:C++ 中用 std::array<char, N> 替代裸数组时,sizeof(arr) 依然有效;但若用 std::vector<char>,必须用 vec.capacity()(且要确保已 reserve),不能用 vec.size()。
格式化字符串里嵌套 %s 时的额外风险
当 %s 对应的源字符串本身不可控(如来自用户输入、文件读取或网络包),即使 snprintf 自身不溢出,源串若没有 \0 结尾,snprintf 会一直读直到越界——这是 undefined behavior,不是 snprintf 的责任范围。
安全做法是:对所有外部来的 char*,先确认它以 \0 结尾,或改用带长度限制的 %.*s:
snprintf(buf, sizeof(buf), "key=%.*s", (int)src_len, src_ptr);
这里 src_len 是你可信的长度上限,避免依赖源串的 \0。
真正麻烦的从来不是记不住 snprintf 的参数顺序,而是忘记检查返回值、混淆 size 和剩余空间、以及对外部字符串的 \0 假设过于乐观。


















