'strncpy'对''sprintf'

问题描述 投票:15回答:4

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

我有一个字符数组:

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

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

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

要么

strncpy(myarray, str, 8);

?

c printf strncpy
4个回答
38
投票

根本不应该使用它们。

  1. sprintf是危险的,被弃用,并被snprintf取代。使用旧的sprintf安全地使用字符串输入的唯一方法是在调用sprintf之前测量它们的长度,这是丑陋且容易出错的,或者通过添加字段精度说明符(例如%.8s%.*s以及大小的额外整数参数)限制)。这也是丑陋且容易出错的,特别是如果涉及多个%s说明符。
  2. strncpy也很危险。它不是strcpy的缓冲区大小限制版本。它是一种将字符复制到固定长度,空填充(与空终止相对)数组的功能,其中源可以是C字符串,也可以是至少是目标大小的固定长度字符数组。它的用途是用于传统的unix目录表,数据库条目等,它们使用固定大小的文本字段,并且不希望在磁盘或内存中浪费单个字节以进行空终止。它可能被误用为缓冲区大小限制的strcpy,但这样做有害有两个原因。首先,如果整个缓冲区用于字符串数据(即,如果源字符串长度至少与dest缓冲区一样长),则无法终止null。您可以自己添加终止,但这很难看并且容易出错。第二,当源字符串比输出缓冲区短时,strncpy总是用空字节填充整个目标缓冲区。这只是浪费时间。

那么你应该用什么呢?

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

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


3
投票

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

  • 它们使用变量参数列表,这使得参数传递更加复杂。这是通过各种模糊的宏和指针完成的。必须在运行时解析所有参数以确定其类型,这会增加额外的开销代码。 (VA列表也是该语言的一个冗余功能,也很危险,因为它的输入功能比普通参数传递要好得多。)
  • 它们必须处理许多复杂的格式并支持所有不同的类型。这也为该功能增加了大量开销。由于所有类型评估都是在运行时完成的,因此编译器无法优化掉从未使用过的函数部分。因此,如果您只想使用printf()打印整数,您将获得与您的程序相关联的浮点数,复杂算术,字符串处理等的支持,因为完全浪费了空间。
  • 另一方面,诸如strcpy()和特别是memcpy()之类的函数由编译器进行了大量优化,通常在内联汇编中实现以获得最大性能。

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

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

编辑

用事实代替主观数字:

目标Freescale 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 

1
投票

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


0
投票

有一种方法可以使用sprintf()(或者如果是偏执的,snprintf())来做一个“安全”的字符串副本,它会截断而不是溢出字段或使其保持非NUL终止。

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

所以:

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

这将unknown_string的内容放入dest_buff,允许终止NUL的空间。

© www.soinside.com 2019 - 2024. All rights reserved.