我可以在我的应用程序中看到许多用于复制字符串的sprintf
。
我有一个字符数组:
char myarray[10];
const char *str = "mystring";
现在,如果我想将字符串str
复制到myarray
,最好使用:
sprintf(myarray, "%s", str);
要么
strncpy(myarray, str, 8);
?
根本不应该使用它们。
sprintf
是危险的,被弃用,并被snprintf
取代。使用旧的sprintf
安全地使用字符串输入的唯一方法是在调用sprintf
之前测量它们的长度,这是丑陋且容易出错的,或者通过添加字段精度说明符(例如%.8s
或%.*s
以及大小的额外整数参数)限制)。这也是丑陋且容易出错的,特别是如果涉及多个%s
说明符。strncpy
也很危险。它不是strcpy
的缓冲区大小限制版本。它是一种将字符复制到固定长度,空填充(与空终止相对)数组的功能,其中源可以是C字符串,也可以是至少是目标大小的固定长度字符数组。它的用途是用于传统的unix目录表,数据库条目等,它们使用固定大小的文本字段,并且不希望在磁盘或内存中浪费单个字节以进行空终止。它可能被误用为缓冲区大小限制的strcpy
,但这样做有害有两个原因。首先,如果整个缓冲区用于字符串数据(即,如果源字符串长度至少与dest缓冲区一样长),则无法终止null。您可以自己添加终止,但这很难看并且容易出错。第二,当源字符串比输出缓冲区短时,strncpy
总是用空字节填充整个目标缓冲区。这只是浪费时间。那么你应该用什么呢?
有些人喜欢BSD strlcpy
功能。在语义上,它与snprintf(dest, destsize, "%s", source)
相同,只是返回值是size_t
并且它不会对字符串长度施加人为的INT_MAX
限制。但是,大多数流行的非BSD系统缺少strlcpy
,并且很容易编写自己的危险错误,所以如果你想使用它,你应该从一个值得信赖的来源获得一个安全的,已知的工作版本。
我的偏好是简单地将snprintf
用于任何非平凡的字符串构造,而strlen
+ memcpy
用于一些被测量为性能关键的琐碎案例。如果你养成了正确使用这个习惯用法的习惯,几乎不可能意外地编写带有字符串相关漏洞的代码。
printf / scanf的不同版本功能非常慢,原因如下:
我曾经在准系统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
我不会仅仅使用sprintf复制字符串。它是矫枉过正的,读取该代码的人肯定会停下来并想知道为什么我这样做,如果他们(或我)遗失了某些东西。
有一种方法可以使用sprintf()(或者如果是偏执的,snprintf())来做一个“安全”的字符串副本,它会截断而不是溢出字段或使其保持非NUL终止。
即使用“*”格式字符作为“字符串精度”,如下所示:
所以:
char dest_buff[32];
....
sprintf(dest_buff, "%.*s", sizeof(dest_buff) - 1, unknown_string);
这将unknown_string的内容放入dest_buff,允许终止NUL的空间。