20

我正在使用 snprintf 将字符串连接到 char 数组:

char buf[20] = "";
snprintf(buf, sizeof buf, "%s%s", buf, "foo");
printf("%s\n", buf);
snprintf(buf, sizeof buf, "%s%s", buf, " bar");
printf("%s\n", buf);

问题是第二个连接buf而不是添加"bar", 替换"foo"它。输出如下:

foo
bar

第一个%s应该保留buf(在这种情况下保持"foo")在那里。第二个%s应该附加"bar"到它。对?

我究竟做错了什么?

4

4 回答 4

32

您违反了restrict上的合同snprintf,该合同规定没有其他参数可以与缓冲区重叠。

无论如何,将输入复制到自身中是一种浪费。 snprintf返回格式化所需的字符数,因此利用它来追加:

char buf[20] = "";
char *cur = buf, * const end = buf + sizeof buf;
cur += snprintf(cur, end-cur, "%s", "foo");
printf("%s\n", buf);
if (cur < end) {
    cur += snprintf(cur, end-cur, "%s", " bar");
}
printf("%s\n", buf);
于 2012-08-22T01:42:54.160 回答
3

试试这个:

char buf[20];
snprintf(buf, sizeof buf, "%s", "foo");
printf("%s\n", buf);
int len = strlen(buf);
snprintf(buf+len, (sizeof buf) - len, "%s", " bar");
printf("%s\n", buf);

输出是“foo bar”。snprintf 的第一个参数,一个指向字符的指针,是它开始填充字符的地方。它不注意缓冲区中的内容。不过,函数 strlen 确实很注意。它计算 snprintf 放在那里的 nul (0) 之前的字符数。所以不要传递 buf,而是传递 buf+strlen(buf)。您也可以使用 strncat,它会稍微更有效率。

我在您的问题下看到了标签 C++。查找 std::string。好多了

于 2012-08-22T01:52:56.563 回答
3

虽然接受的答案没问题,但更好的(在我看来)答案是连接字符串是错误的。您应该在一次调用中构造整个输出snprintf。这就是使用格式化输出函数的全部意义所在,它比指针运算和多次调用更高效、更安全。例如:

snprintf(buf, sizeof buf, "%s%s%s", str_a, str_b, str_c);
于 2012-08-22T02:28:00.447 回答
2

为什么不使用strncat()?它旨在做到这一点:

char buf[20] = "";
strncat(buf, "foo", sizeof buf);
printf("%s\n", buf);
strncat(buf, " bar", sizeof buf - strlen(buf));
printf("%s\n", buf);

如果您的系统支持它,您可以使用strncat_s()而不是strncat,因为它具有额外的溢出保护级别,并且无需计算输出缓冲区中剩余的字节数。

如果必须使用snprintf,则需要创建一个单独的指针来跟踪字符串的结尾。该指针将是您传递给的第一个参数snprintf。您当前的代码始终使用buf,这意味着它将始终打印到该数组的开头。您可以strlen在每次调用后使用来查找字符串的结尾snprintf,也可以使用的返回值snprintf来递增指针。

于 2012-08-22T01:54:00.193 回答