你很可能不懂 snprintf

Q: snprintf 返回的是实际写入到缓冲区的字符数吗?

A: 错。当缓冲区空间不足时会返回比缓存区空间大的值。

man 3 snprintf

摘录关键部分

原型写义

#include <stdio.h>

int sprintf(char *str, const char *format, …);

int snprintf(char *str, size_t size, const char *format, …);

功能描述

The functions in the printf() family produce output according to a format as described below. The functions printf() and vprintf() write output to stdout, the standard output stream; fprintf() and vfprintf() write output to the given output stream; sprintf(), snprintf(), vsprintf() and vsnprintf() write to the character string str.

The function dprintf() is the same as fprintf(3) except that it outputs to a file descriptor, fd, instead of to a stdio stream.

The functions snprintf() and vsnprintf() write at most size bytes (including the terminating null byte ('\0')) to str.

返回值

Upon successful return, these functions return the number of characters printed (excluding the null byte used to end output to strings).

The functions snprintf() and vsnprintf() do not write more than size bytes (including the terminating null byte ('\0')). If the output was truncated due to this limit, then the return value is the number of charac‐ ters (excluding the terminating null byte) which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated. (See also below under NOTES.)

If an output error is encountered, a negative value is returned.

了解 snprintf

sprintf 输出到缓冲区,提供的缓存区空间不足时,引发臭名昭著的 缓存区溢出 漏洞,snprintf 通过指定缓存区空间大小解决了这个问题。

snprintf 常用于字符串格式化(如:拼接 SQL 或 shell 命令),很多人会用它的返回值来指定下一次拼接的起始位置。

如下代码所示:

const char* fields[] = {
    "name",
    "age",
    "city"
};

char sql[10] = "select ";
int pos = 0;

for(int i = 0; i < sizeof(fields)/sizeof(fields[0]); ++i) {
    pos += snprintf(sql + pos, sizeof(sql) - pos, "%s%s", (i ? ", " : ""), fields[i]);
}

snprintf(sql + pos, sizeof(sql) - pos, " from users");

上面的代码没有处理缓存区不足的问题,最坏的结果仅仅是因缓存区空间不足而导致 sql 不完整吗?

比那要严重多了,它还会导致”缓存区溢出“漏洞。

这是因为,当缓冲区尺寸不足时,snprintf 会返回比缓冲区尺寸大的值,最后会导致传给 snprintf 的缓存区尺寸值为负数,转化为无符号整型(size_t)就是一个超大的整数值。

使用 snprintf 的正确姿势

int pos = 0;
int n = snprintf(buffer + pos, sizeof(buffer) - pos, "%s", "hello");
if (n >= sizeof(buffer) - pos) {
    // a return value of size or more means that the output was truncated
    fprintf(stderr, "error: buffer size not enough\n");
    return;
}
pos += n;

n = snprintf(buffer + pos, sizeof(buffer) - pos, "%s", " snprintf");
if (n >= sizeof(buffer) - pos) {
    // a return value of size or more means that the output was truncated
    fprintf(stderr, "error: buffer size not enough\n");
    return;
}
pos += n;

下次在代码里,当你看到有人用 snprintf 进行”漂亮“的拼接,相信你会从”哇“改口为”操“了。


c