'strncpy'对''sprintf'

时间:2012-09-05 06:06:48

标签: c printf strncpy

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

我有一个字符数组:

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

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

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

strncpy(myarray, str, 8);

4 个答案:

答案 0 :(得分:38)

根本不应该使用它们。

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

  2. %s也很危险。 strncpy的缓冲区大小限制版本。它是一个将字符复制到固定长度的null- 填充(而不是null- 终止)数组的函数,其中源可以是C字符串或固定长度字符数组,至少是目标的大小。它的用途是用于传统的unix目录表,数据库条目等,它们使用固定大小的文本字段,并且不希望在磁盘或内存中浪费单个字节以进行空终止。它可以被误用为缓冲区大小限制strcpy,但这样做有两个原因是有害的。首先,如果整个缓冲区用于字符串数据(即,如果源字符串长度至少与dest缓冲区一样长),则无法终止null。您可以自己添加终止,但这很难看并且容易出错。第二,当源字符串比输出缓冲区短时,strcpy总是用空字节填充整个目标缓冲区。这只是浪费时间。

  3. 那么您应该使用什么呢?

    有些人喜欢BSD strncpy功能。在语义上,它与strlcpy相同,只是返回值为snprintf(dest, destsize, "%s", source)并且它不会对字符串长度施加人为size_t限制。但是,大多数流行的非BSD系统缺少INT_MAX,并且很容易编写自己的危险错误,所以如果你想使用它,你应该从一个值得信赖的来源获得一个安全的,已知的工作版本。 / p>

    我的偏好是简单地使用strlcpy进行任何重要的字符串构建,而snprintf + strlen则用于一些被测量为性能关键的琐碎案例。如果你养成了正确使用这个习惯用法的习惯,几乎不可能意外地编写带有字符串相关漏洞的代码。

答案 1 :(得分:3)

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

  • 他们使用变量参数列表,这使得参数传递更加复杂。这是通过各种模糊的宏和指针完成的。必须在运行时解析所有参数以确定其类型,这会增加额外的开销代码。 (VA列表也是该语言的一个冗余功能,并且也很危险,因为它比普通参数传递具有更好的输入。)

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

  • 另一方面,strcpy(),特别是memcpy()等函数由编译器进行了大量优化,通常在内联汇编中实现,以获得最佳性能。

我曾在裸机16位低端微控制器上进行的一些测量包含在下面。

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

修改

用事实取代主观数字:

目标飞思卡尔HCS12上的strcpy与sprintf的测量,编译器飞思卡尔 Codewarrior 5.1。使用C90实现的sprintf,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 

答案 2 :(得分:1)

我不会仅仅使用sprintf复制字符串。它太过分了,读取该代码的人肯定会停下来,想知道为什么我这样做了,如果他们(或我)遗失了什么。

答案 3 :(得分:0)

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

将“ *”格式字符用作“字符串精度”,如下所示:

所以:

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

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