C 用于字符串连接的snprintf

C 用于字符串连接的snprintf,c,string,C,String,我使用snprintf将字符串连接到字符数组: 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(在本例

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

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”
。对吧?


我做错了什么?

您违反了
snprintf
上的
restrict
合同,该合同规定其他参数不能与缓冲区重叠

无论如何,将输入复制到自身是一种浪费
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);
charbuf[20]=”;
char*cur=buf,*const end=buf+sizeof buf;
cur+=snprintf(cur,end cur,“%s”,“foo”);
printf(“%s\n”,buf);
如果(当前<结束){
cur+=snprintf(cur,end cur,“%s”,“bar”);
}
printf(“%s\n”,buf);
试试这个:

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);
输出为“foobar”。snprintf的第一个参数是指向字符的指针,它将从这里开始填充字符。它不注意缓冲区中已经存在的内容。不过strlen的功能确实很注意。它统计snprintf放入nul(0)之前的字符数。因此,不要通过buf,而是通过buf+strlen(buf)。您还可以使用strncat,这会稍微更有效一些

<>我在你的问题下面看到了标签C++。查找std::string。更好。

为什么不使用?它的设计正是为了做到这一点:

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
的返回值来增加指针。

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

snprintf(buf, sizeof buf, "%s%s%s", str_a, str_b, str_c);


对于C++,你应该咬紧牙关,只使用<代码> STD::String 。当然,这对你的问题的C部分没有帮助。@Scooter:不要从使用C库函数的问题中删除
C++
标记。在C语言与C++编译器之间如何处理同一代码时,常常会有细微的差别。@ Beoviigt我不能直接改变任何标记,所以这意味着至少有两个人不同意你的看法。在网上不难找到三个错的人@杰敏:别那么做。现在我将删除“<代码> C++ >代码>标签,基于您的注释,它毕竟不是编译为C++。有一个文件我可以参考这个代码<限制< /Cult>合同?@ Jermin:您的意思是它适用于<代码> SNPROTFF < /C> >还是C99代码>的含义限制关键字?首先,请参见。第二,任何最近出版的优秀的C语言书籍,或者.Google都应该比其他任何东西更多地使用手册页。我指的是这个。谢谢。@JerminBazazian-
buf
是静态分配的,您不能扩展它。你能解释一下你的意思吗?@Jermin:
snprintf
不能溢出。您在缓冲区中给它剩余的空间,它不会写超过那么多的
char
s。正如所建议的那样
snprintf
是最干净的方法。但我不太清楚为什么。我的意思是strncat也是溢出安全的。@JerminBazazian-
snprintf
建议在较旧(C99之前)的系统上使用,在这些系统中,
strncat
不可用,最好的是
strcat
,它有溢出问题
strncat
没有这个问题。我假设使用了
snprintf
,因为它提供了一整套格式化功能,而
strncat
没有。不确定,但我认为缓冲区大小有一个错误,因为根据您的链接,它不包括总是附加的NUL字节。
strncat
不是为输出缓冲区的安全而设计的。它被设计用于不安全地将非C字符串文本数据(从固定大小的非空终止字段)附加到C字符串。我在curl中使用此方法进行写回调,并且所有字符串事先都不知道。我一次有两根弦。现有缓冲区和要连接的新缓冲区。因此,它必须是多个调用,而不是一个。这种想法与循环和条件完全不兼容。因此,我认为它实际上没有多大帮助,除非是在非常有限的情况下。@BenVoigt:我认为从经验上看,这些“有限的情况”涵盖了相当大一部分的实际使用情况,除非代码被编写成毫无意义的抽象,而不是简单高效。当可以直接用静态格式字符串构造字符串时,用循环和条件构造字符串只会使阅读代码的人更难理解它在做什么。当然也有很多例外情况(例如,如果您正在格式化“自然”为数组或复杂数据结构的数据),但OP的代码只是有简单的连续调用。@R..:我很确定真正的代码不包含“foo”和“bar”。闻起来很香