‘strncpy’与’sprintf’

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

我有一个字符数组:

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

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

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

要么

 strncpy(myarray, str, 8); 

根本不应该使用它们。

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

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

那么你应该用什么呢?

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

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

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

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

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

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

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

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

编辑

用事实代替主观数字:

目标Freescale HCS12上的strcpy与sprintf的测量,编译器Freescale 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 

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