【问题标题】:snprintf for string concatenationsnprintf 用于字符串连接
【发布时间】:2012-08-22 01:39:36
【问题描述】:

我正在使用 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" 到它。对吧?

我做错了什么?

【问题讨论】:

  • 对于 C++,你应该硬着头皮使用std::string。当然,这对您问题的 C 部分没有帮助。
  • @Scooter:不要到处从使用 C 库函数的问题中删除 c++ 标记。 C 与 C++ 编译器处理相同代码的方式通常存在细微差别。
  • @BenVoigt 我不能直接更改任何标签,所以这意味着至少有两个人不同意你。
  • @Scooter:我不在乎有多少人同意你。在互联网上不难找到三个错的人。 The expert consensus is that the tag should match the compiler being used.
  • @Jermin:不要那样做。现在我将删除 c++ 标签,根据您的评论,它毕竟没有编译为 C++。

标签: c string


【解决方案1】:

您违反了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);

【讨论】:

  • 这个restrict合同有没有我可以参考的文件?
  • @Jermin:你的意思是看到它适用于snprintf,或者C99 restrict关键字的含义?第一个,请参阅the man page。第二,任何最近的好 C 书,或wikipedia
  • Google 应该更多地使用手册页。我指的是这个link。谢谢。
  • @JerminBazazian- buf 是静态分配的,不能扩展。你能解释一下你的意思吗?
  • @Jermin:snprintf 不能溢出。你给它缓冲区中的剩余空间,它不会写超过那么多chars。
【解决方案2】:

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

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

【讨论】:

  • 我在 curl 中使用这种方法进行写回调,并且所有字符串都是事先不知道的。我一次有两个字符串。现有缓冲区和要连接的新缓冲区。所以,它必须是多个调用而不是一个。
  • 这个想法与循环和条件完全不兼容。所以我不认为它实际上很有帮助,除非在非常有限的情况下。
  • @BenVoigt:我认为凭经验,这些“有限情况”涵盖了相当大部分的实际使用情况,除非代码被编写为无偿抽象而不是简单高效。当可以直接使用静态格式字符串构造时,构造带有循环和条件的字符串只会使阅读代码的人更难理解它在做什么。当然有很多例外情况(例如,如果您正在格式化“自然”为数组或复杂数据结构的数据),但 OP 的代码只是有平坦的连续调用。
  • @R..:我很确定真正的代码不包含“foo”和“bar”。闻起来很像最小的复制品。
【解决方案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 的第一个参数,一个指向 char 的指针,是它开始填充字符的地方。它不注意缓冲区中的内容。不过,函数 strlen 确实很注意。它计算 snprintf 放在那里的 nul (0) 之前的字符数。所以不要传递 buf,而是传递 buf+strlen(buf)。也可以使用 strncat,效率会稍微高一些。

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

【讨论】:

    【解决方案4】:

    为什么不使用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,这意味着它将始终打印到该数组的开头。您可以在每次调用snprintf 后使用strlen 查找字符串的结尾,也可以使用snprintf 的返回值来递增指针。

    【讨论】:

    • 建议here snprintf 是最干净的方式。虽然不太清楚为什么。我的意思是 strncat 也是溢出安全的。
    • @JerminBazazian- snprintf 建议在旧的(C99 之前的)系统上使用,其中strncat 不可用,最好的是strcat,它存在溢出问题。 strncat 没有这个问题。
    • 我假设使用了snprintf,因为它提供了strncat 没有的大量格式化功能。
    • 不确定,但我认为您的缓冲区大小有一个错误,因为根据您的链接,它不包括始终附加的 NUL 字节。
    • strncat 不是为输出缓冲区的安全而设计的。它被设计用于不安全地将非 C 字符串文本数据(来自固定大小的非空终止字段)附加到 C 字符串。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    • 1970-01-01
    • 2016-11-22
    相关资源
    最近更新 更多