18

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

我有一个字符数组:

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

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

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

或者

strncpy(myarray, str, 8);

?

4

4 回答 4

50

根本不应该使用。

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

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

那么你应该改用什么?

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

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

于 2012-09-05T06:55:53.567 回答
3

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

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

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

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

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

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

编辑

用事实代替主观数字:

在目标 Freescale HCS12、编译器 Freescale Codewarrior 5.1 上测量 strcpy 与 sprintf。使用 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 
于 2012-09-05T06:28:03.397 回答
1

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

于 2012-09-05T06:27:50.423 回答
0

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

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

所以:

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

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

于 2019-04-10T09:18:18.303 回答