【问题标题】:'strncpy' vs. 'sprintf'“strncpy”与“sprintf”
【发布时间】:2012-08-29 19:34:11
【问题描述】:

我可以看到很多sprintf在我的应用程序中用于复制字符串。

我有一个字符数组:

char myarray[10];
const char *str = "mystring";

现在如果我想将字符串str复制到myarray,最好使用:

sprintf(myarray, "%s", str);

strncpy(myarray, str, 8);

?

【问题讨论】:

  • 不要忘记在调用 strncpy 之后添加一个 '\0'。它可能不会自己做。
  • 在哪个维度上更好?性能 - 然后是基准!虽然 strncpy 应该更快 - 它做的更少。记得加\0
  • This blog post 很好地解释了这些差异。对大多数人来说最大的不同是snprintf 告诉你它写了多少个字符,你不必担心在空间不足时会丢失空终止符。
  • 那么 strcpy 是否比 strncpy 好,因为它还复制源字符串中的终止空字符?
  • strncpy 可能不添加终止符 '\0' 的原因是因为它最初是为了将文本放入固定长度的小字段中,如果完全填充则不需要终止。

标签: c printf strncpy


【解决方案1】:

有一种方法可以使用 sprintf() (或者如果是偏执狂, snprintf() )进行“安全”的字符串复制,它会截断而不是溢出字段或使其非 NUL 终止。

即使用“*”格式字符作为“字符串精度”如下:

所以:

char dest_buff[32];
....
sprintf(dest_buff, "%.*s", sizeof(dest_buff) - 1, unknown_string);

这会将 unknown_string 的内容放入 dest_buff 中,为终止的 NUL 留出空间。

【讨论】:

    【解决方案2】:

    不同版本的 printf/scanf 函数非常慢,原因如下:

    • 它们使用可变参数列表,这使得参数传递更加复杂。这是通过各种晦涩难懂的宏和指针来完成的。所有参数都必须在运行时解析以确定它们的类型,这增加了额外的开销代码。 (VA 列表也是该语言的一个相当多余的特性,而且也很危险,因为它的类型比普通的参数传递要弱得多。)

    • 它们必须处理大量复杂的格式并支持所有不同的类型。这也增加了函数的大量开销。由于所有类型评估都是在运行时完成的,因此编译器无法优化掉函数中从未使用过的部分。因此,如果您只想使用 printf() 打印整数,您将获得与程序相关联的浮点数、复数算术、字符串处理等的支持,这完全是浪费空间。

    • 另一方面,诸如 strcpy() 和特别是 memcpy() 之类的函数已由编译器进行了高度优化,通常在内联汇编中实现以获得最佳性能。

    我曾经在准系统 16 位低端微控制器上进行的一些测量包括在下面。

    根据经验,您不应该在任何形式的生产代码中使用 stdio.h。它被认为是一个调试/测试库。 MISRA-C:2004 在生产代码中禁止 stdio.h。

    编辑

    用事实代替主观数字:

    在目标 Freescale HCS12、编译器 Freescale 上测量 strcpy 与 sprintf 代码战士 5.1。使用 sprintf 的 C90 实现,C99 会更加无效。启用所有优化。以下代码经过测试:

      const char str[] = "Hello, world";
      char buf[100];
    
      strcpy(buf, str);
      sprintf(buf, "%s", str);
    

    执行时间,包括参数改组开/关调用栈:

    strcpy   43 instructions
    sprintf  467 instructions
    

    分配的程序/ROM空间:

    strcpy   56 bytes
    sprintf  1488 bytes
    

    分配的 RAM/堆栈空间:

    strcpy   0 bytes
    sprintf  15 bytes
    

    内部函数调用次数:

    strcpy   0
    sprintf  9
    

    函数调用堆栈深度:

    strcpy   0 (inlined)
    sprintf  3 
    

    【讨论】:

    • 这个答案充满了糟糕的建议和错误信息。绝对没有理由说明为什么不应在生产代码中使用 stdio,实际上我认为 snprintf 是标准库中您应该在安全意识代码中使用的唯一字符串函数处理字符串。性能成本被高度夸大了(除了持续的开销时间之外,它不应该花费超过strlen/memcpy 甚至可能在内部使用这些),还有危险(任何现代编译器都会标记格式字符串不匹配你有警告)。
    • @R.. 关于安全和生产代码,我确实引用了一个非常知名和广泛认可的关键应用程序权威,也就是 MISRA-C:2004 规则 20.9。如果你不这么认为,你作为一个随机的互联网人的主观意见并不重要,请引用另一个支持你的观点的权威。
    • 正如预期的那样,您的测量结果显示成本最低。成本看起来很简单的唯一原因是你的字符串太短了。如果您尝试 1k 或 20k 字符串,您会发现性能几乎相同。
    • @Lundin:我不是说要写你自己的strcpy。我说的是通过将多个字符串或数字输入与字符串放在一起来组装字符串。当然你可以用strlenmemcpy 自己做,但每次你做的时候,都有很大的概率犯错误(一个接一个的错误,错别字等),如果你不存在的话使用了一个标准的、安全的习语。此外,与 strlenmemcpy 调用的字符串相比,使用 snprintf 的调用代码通常会变小。
    • +1 表示至少在解释事实方面做出了很好的尝试,并且也花了很多时间发表评论。
    【解决方案3】:

    根本不应该使用。

    1. sprintf 是危险的,已弃用,并已被snprintf 取代。安全地使用旧的sprintf 与字符串输入的唯一方法是在调用sprintf 之前测量它们的长度,这很难看且容易出错,或者通过添加字段精度说明符(例如%.8s%.*s带有一个额外的整数参数作为大小限制)。这也很丑陋且容易出错,尤其是在涉及多个 %s 说明符的情况下。

    2. strncpy 也很危险。它不是strcpy 的缓冲区大小限制版本。它是一个用于将字符复制到固定长度、null-padded(与 null-terminated 相对)数组中的函数,其中源可以是 C 字符串或固定长度的字符数组至少是目标的大小。它的预期用途是用于与固定大小的文本字段一起使用的遗留 unix 目录表、数据库条目等,并且不希望在磁盘或内存中浪费一个字节以进行空终止。它可能被误用作缓冲区大小限制strcpy,但这样做是有害的,原因有两个。首先,如果整个缓冲区用于字符串数据(即,如果源字符串长度至少与目标缓冲区一样长),则它无法空终止。您可以自己添加终止,但这很丑陋且容易出错。其次,当源字符串比输出缓冲区短时,strncpy 总是用空字节填充整个目标缓冲区。这简直是​​浪费时间。

    那么你应该改用什么?

    有些人喜欢 BSD 的 strlcpy 功能。从语义上讲,它与snprintf(dest, destsize, "%s", source) 相同,只是返回值为size_t,并且它没有对字符串长度施加人为的INT_MAX 限制。但是,大多数流行的非 BSD 系统都缺少 strlcpy,而且编写自己的代码很容易出错,因此如果您想使用它,您应该从可信赖的来源获取安全、已知工作的版本。

    我的偏好是简单地将snprintf 用于任何非平凡的字符串构造,而strlen+memcpy 用于一些经测量对性能至关重要的平凡案例。如果你养成了正确使用这个习语的习惯,几乎不可能意外地编写带有字符串相关漏洞的代码。

    【讨论】:

    • +1。我要争辩说strncpy 并不危险,因为它非常适合其预期用途,但我发现它经常被误解、误用和滥用,所以说它很危险更简单。
    • 我认为strncpy 的“预期用途”主要是一个已经过时的概念......
    【解决方案4】:

    我不会使用 sprintf 来复制一个字符串。这太过分了,阅读该代码的人肯定会停下来想知道我为什么这样做,以及他们(或我)是否遗漏了什么。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-20
      相关资源
      最近更新 更多