【问题标题】:snprintf vs. strcpy (etc.) in CC 中的 snprintf 与 strcpy (等)
【发布时间】:2010-04-09 10:12:28
【问题描述】:

为了进行字符串连接,我一直在做基本的strcpystrncpy 的 char* 缓冲区。然后我了解了snprintf和朋友们。

我应该坚持我的strcpystrcpy + \0 终止吗?还是我以后应该只使用snprintf

【问题讨论】:

  • strncpy 在这里完全不合适。 strncpy 不应用于有限长度的字符串复制。这不是该功能的作用,也不是它的开发目的。在这种情况下,snprintf 的适当对应物是 strlcpy(尽管它不是标准的)。

标签: c string printf


【解决方案1】:

对于大多数用途,我怀疑使用 strncpysnprintf 之间的区别是可衡量的。

如果涉及任何格式,我倾向于只使用snprintf,而不是混合使用strncpy

我发现这有助于代码清晰,意味着您可以使用以下习惯用法来跟踪您在缓冲区中的位置(从而避免创建Shlemiel the Painter 算法):

char sBuffer[iBufferSize];
char* pCursor = sBuffer;

pCursor += snprintf(pCursor, sizeof(sBuffer) - (pCursor - sBuffer),  "some stuff\n");

for(int i = 0; i < 10; i++)
{
   pCursor += snprintf(pCursor, sizeof(sBuffer) - (pCursor - sBuffer),  " iter %d\n", i);
}

pCursor += snprintf(pCursor, sizeof(sBuffer) - (pCursor - sBuffer),  "into a string\n");

【讨论】:

  • 如果您只调用一次snprintf 会更清楚。请注意,如果缓冲区不够大,snprintf 将返回必要的缓冲区大小(或失败时为负数),因此像这样递增 pCursor 可能很危险。
  • 在您的代码中执行 s/snprint/snprintf/。另外,你的例子没有意义。我会坚持使用格式化程序。
  • @jweyrich - 哎呀,感谢固定的函数名称。是的,这是一个愚蠢的例子,让它变得更有用了。
  • @jamesdlin - 是的,我知道,要么需要知道字符串大小的界限,要么需要进行一些错误检查。
  • man snprintf "如果遇到输出错误,则返回负值。"
【解决方案2】:
如果要格式化字符串,

snprintf 会更加健壮。如果您只想连接,请使用 strncpy(不要使用 strcpy),因为它更有效。

【讨论】:

  • 在 strncpy 上引用 Linux man 的名言:“警告:如果 src 的前 n 个字节中没有空字节,则放在 dest 中的字符串不会以空值结尾。”所以这不仅仅是关于效率。
【解决方案3】:

正如其他人已经指出的那样:不要使用 strncpy。

  • strncpy 不会在截断的情况下以零终止。
  • 如果字符串比缓冲区短,strncpy 将对整个缓冲区进行零填充。如果缓冲区很大,这可能会消耗性能。

snprintf 将(在 POSIX 平台上)零终止。在 Windows 上,只有 _snprintf,它不会零终止,因此请考虑到这一点。

注意:使用 snprintf 时,请使用这种形式:

snprintf(buffer, sizeof(buffer), "%s", string);

而不是

snprintf(buffer, sizeof(buffer), string);

后者是不安全的,并且 - 如果字符串取决于用户输入 - 可能会导致堆栈粉碎等。

【讨论】:

  • 这种不一致对于 libc 来说是一个非常令人困惑的部分。
【解决方案4】:

sprintf 有一个非常有用的返回值,它允许高效的追加。

这是成语:

char buffer[HUGE] = {0}; 
char *end_of_string = &buffer[0];
end_of_string += sprintf( /* whatever */ );
end_of_string += sprintf( /* whatever */ );
end_of_string += sprintf( /* whatever */ );

你明白了。这是有效的,因为sprintf 返回它写入缓冲区的字符数,因此将缓冲区推进这么多位置将使您在到目前为止所写内容的末尾指向'\0'。因此,当您将更新后的位置交给下一个sprintf 时,它就可以在那里开始写入新字符。

strcpy对比,要求其返回值无用。它会将您传递的相同论点交还给您。所以附加strcpy 意味着遍历整个第一个字符串以寻找它的结尾。然后再次附加另一个 strcpy 调用意味着遍历整个第一个字符串,然后是现在位于它之后的第二个字符串,寻找'\0'。第三个strcpy 将再次遍历已经写入的字符串。等等。

因此,对于非常大的缓冲区的许多小追加,strcpy 接近 (O^n) 其中 n 是追加的数量。这太可怕了。

另外,正如其他人所提到的,他们做不同的事情。 sprintf 可用于将数字、指针值等格式化到缓冲区中。

【讨论】:

  • 糟糕,我刚刚注意到您专门询问了 snprintf。该习语仍然可以使用,但您还需要持续计算缓冲区中剩余的字符数。然后它变成written = snprintf(buf, remanining, ...); buf += written; remaining -= written;
  • snprintf 需要格外小心。请参阅我对此的评论here's answer。
  • 如您所见,问题是关于snprintf() 而不是sprintf()。但请注意来自snprintf() 的返回值中的错误!可能是负面的!见这里:cplusplus.com/reference/cstdio/snprintf.
【解决方案5】:

所有 *printf 函数都会检查格式并扩展其相应的参数,因此它比简单的 strcpy/strncpy 慢,后者仅从线性内存中复制给定数量的字节。

我的经验法则是:

  • 在需要格式化时使用 snprintf。
  • 只需要复制一块线性内存时,坚持使用strncpy/memcpy。
  • 只要您确切地知道要复制的缓冲区的大小,就可以使用 strcpy。如果您无法完全控制缓冲区大小,请不要使用它。

【讨论】:

    【解决方案6】:

    我认为 strncpy 和 snprintf 之间还有另一个区别。

    想一想:

    const int N=1000000;
    char arr[N];
    strncpy(arr, "abce", N);
    

    通常,strncpy 会将目标缓冲区的其余部分设置为“\0”。这将花费大量的 CPU 时间。而当你调用 snprintf 时,

    snprintf(a, N, "%s", "abce");
    

    它将保持缓冲区不变。

    我不知道为什么strncpy会那样做,但是在这种情况下,我会选择snprintf而不是strncpy。

    【讨论】:

      【解决方案7】:

      strcpy、strncpy 等仅将字符串从一个内存位置复制到另一个内存位置。但是,使用 snprint,您可以执行更多操作,例如格式化字符串。将整数复制到缓冲区等

      这完全取决于您需要使用哪一个。如果按照您的逻辑,strcpy 和 strncpy 已经为您工作,则无需跳转到 snprintf。

      另外,请记住按照其他人的建议使用 strncpy 以获得更好的安全性。

      【讨论】:

      • 我知道你的意思,但strncpy 不限于读取(甚至写入)以 NUL 结尾的字符串。
      • 是的,我知道。可能,我没有正确表达我的答案。我已经编辑过了。感谢您指出。
      【解决方案8】:

      strncpy 和 snprintf 之间的区别在于 strncpy 基本上由您负责以 '\0' 终止字符串。它可以用 '\0' 终止 dst,但前提是 src 足够短。

      典型的例子是:

      strncpy(dst, src, n);
      // if src is longer than n dst will not contain null
      // terminated string at this point
      dst[n - 1] = '\0';
      
      snprintf(dst, n, "%s", src); // dst will 100% contain null terminated string
      

      【讨论】:

        猜你喜欢
        • 2019-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-24
        • 2011-02-23
        • 2012-12-10
        • 2021-11-11
        • 2013-01-06
        相关资源
        最近更新 更多