我在我的应用程序中看到很多使用 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 目录表、数据库条目等,这些数据工作在固定大小的文本字段上,并且不想在磁盘或内存中为 null 终止浪费一个字节。它可以被误用为缓冲区大小受限的 strcpy
,但这样做有两个害处。首先,如果整个缓冲区用于字符串数据,则它无法 null 终止 (即如果源字符串长度至少与目标缓冲区一样长)。你可以自己添加终止符,但这样做很丑陋和容易出错。其次,当源字符串短于输出缓冲区时,strncpy
总是用空字节填充完整的目标缓冲区。这只是浪费时间。
那么你应该使用什么呢?
有些人喜欢BSD的strlcpy
函数。从语义上讲,它与snprintf(dest, destsize, "%s", source)
相同,除了返回值是size_t
并且没有对字符串长度施加人为的INT_MAX
限制。然而,大多数流行的非BSD系统缺少strlcpy
,自己编写很容易出现危险错误,因此如果想使用它,应该从可靠的来源获取一个安全、已知工作的版本。
我的偏好是对于任何不平凡的字符串构造都直接使用snprintf
,对于一些被证明是性能关键的简单情况可以使用strlen
+memcpy
。如果你养成正确使用这个习惯,就几乎不可能意外地写出存在字符串相关漏洞的代码。
strncpy
并不危险,因为它完美地适合其预期用途,但我发现它经常被误解、滥用和虐待,所以简单来说,它是危险的。 - CB Baileystrncpy
的“预期用途”大多已经过时,这个概念不再有用... - R.. GitHub STOP HELPING ICEprintf/scanf的不同版本是非常慢的函数,原因如下:
它们使用可变参数列表,使得参数传递更加复杂。这是通过各种晦涩的宏和指针实现的。所有参数都必须在运行时解析以确定它们的类型,这增加了额外的开销代码。(VA列表也是语言中相当冗余的功能,而且很危险,因为它比纯参数传递具有更弱的类型。)
它们必须处理大量复杂的格式和所有支持的不同类型。这也给函数添加了大量开销。由于所有类型评估都是在运行时完成的,编译器无法优化从未使用过的函数部分。因此,如果你只想用printf()打印整数,你将得到与你的程序链接的浪费空间的支持,例如浮点数、复杂数学运算、字符串处理等等。
另一方面,像strcpy()和特别是memcpy()这样的函数被编译器进行了大量优化,通常采用内联汇编实现,以获得最佳性能。
下面包括我曾经在裸机16位低端微控制器上做过的一些测量。
一般来说,您不应在任何生产代码中使用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);
执行时间,包括参数混洗(on/off)、调用堆栈:
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
snprintf
是你应该在处理字符串时使用的 唯一 标准库字符串函数,尤其是在安全意识较高的代码中使用。性能成本被高度夸大(除了常数开销时间之外,它不应该比strlen
/memcpy
更慢,很可能内部使用这些函数),危险也被夸大了(任何现代编译器都会通过警告提示格式字符串不匹配)。 - R.. GitHub STOP HELPING ICEstrcpy
。我是在谈论通过将多个字符串或数字输入与字符串组合来组装字符串。当然,您可以使用strlen
和memcpy
自己完成它,但每次这样做时,都存在犯错误(偏移一个字符,拼写错误等)的非微不足道的概率,如果您使用标准的安全习惯用法,则不存在这些错误。此外,与strlen
和memcpy
调用的字符串相比,使用snprintf
通常使调用代码更小。 - R.. GitHub STOP HELPING ICE有一种使用sprintf()(或者如果你很谨慎,可以使用snprintf())来进行“安全”字符串复制的方法,它会截断而不是溢出字段或使其未被NUL终止。
这就是使用“*”格式字符作为“字符串精度”的方法:
所以:
char dest_buff[32];
....
sprintf(dest_buff, "%.*s", sizeof(dest_buff) - 1, unknown_string);
这将未知字符串的内容放入dest_buff中,为终止NUL留出空间。
strcpy()
。如果您知道源字符串的长度,可以使用memmove()
或memcpy()
代替strcpy()
。请注意,如果您指定的第三个参数长度远大于字符串长度,则strncpy()
可能非常低效,因为strncpy()
会将输出填充到完整长度。正如其他人指出的那样,strncpy()
不能保证输出以空字符结尾。 - Jonathan Lefflerstrncpy
函数可能不会添加终止符'\0'
的原因是,它最初是为了将文本放入不需要终止符的小型固定长度字段中而设计的,如果完全填充则不需要终止符。 - Some programmer dude