'strncpy'和'sprintf'的区别

23

我在我的应用程序中看到很多使用 sprintf 的地方来复制字符串。

我有一个字符数组:

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

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

sprintf(myarray, "%s", str);
或者
strncpy(myarray, str, 8);

->

请提供需要翻译的内容。


5
调用strncpy后不要忘记添加 '\0',因为它可能不会自动添加。 - Thomas Padron-McCarthy
3
在哪个方面更好?表现 - 那就进行基准测试!虽然strncpy应该更快 - 因为它做的更少。记得添加\0。 - Drakosha
那么,由于它还复制源字符串中的终止空字符,strcpy是否比strncpy更好呢? - Vijay
只有在您知道源字符串和目标字符串的长度时,才能安全地使用 strcpy()。如果您知道源字符串的长度,可以使用 memmove()memcpy() 代替 strcpy()。请注意,如果您指定的第三个参数长度远大于字符串长度,则 strncpy() 可能非常低效,因为 strncpy() 会将输出填充到完整长度。正如其他人指出的那样,strncpy() 不能保证输出以空字符结尾。 - Jonathan Leffler
3
strncpy 函数可能不会添加终止符 '\0' 的原因是,它最初是为了将文本放入不需要终止符的小型固定长度字段中而设计的,如果完全填充则不需要终止符。 - Some programmer dude
显示剩余3条评论
4个回答

54

两者都不应该使用。

  1. sprintf 是危险的,已被弃用,并被 snprintf 取代。要安全地使用旧版的 sprintf,必须在调用 sprintf 之前测量字符串长度,这样做很丑陋且容易出错,或者添加一个字段精度说明符 (例如 %.8s%.*s,需要额外的整数参数限制大小)。特别是如果涉及多个 %s 说明符,这样做就更加丑陋和容易出错。

  2. strncpy 也是危险的。它不是 strcpy 的缓冲区大小受限版本。它是将字符复制到固定长度的、空值填充 (与以空值结尾相反) 的数组中的函数,其中源可以是 C 字符串或至少与目标大小相同的固定长度字符数组。它旨在用于遗留的 unix 目录表、数据库条目等,这些数据工作在固定大小的文本字段上,并且不想在磁盘或内存中为 null 终止浪费一个字节。它可以被误用为缓冲区大小受限的 strcpy,但这样做有两个害处。首先,如果整个缓冲区用于字符串数据,则它无法 null 终止 (即如果源字符串长度至少与目标缓冲区一样长)。你可以自己添加终止符,但这样做很丑陋和容易出错。其次,当源字符串短于输出缓冲区时,strncpy 总是用空字节填充完整的目标缓冲区。这只是浪费时间。

那么你应该使用什么呢?

有些人喜欢BSD的strlcpy函数。从语义上讲,它与snprintf(dest, destsize, "%s", source)相同,除了返回值是size_t并且没有对字符串长度施加人为的INT_MAX限制。然而,大多数流行的非BSD系统缺少strlcpy,自己编写很容易出现危险错误,因此如果想使用它,应该从可靠的来源获取一个安全、已知工作的版本。

我的偏好是对于任何不平凡的字符串构造都直接使用snprintf,对于一些被证明是性能关键的简单情况可以使用strlen+memcpy。如果你养成正确使用这个习惯,就几乎不可能意外地写出存在字符串相关漏洞的代码。


+1.我本来想辩论strncpy并不危险,因为它完美地适合其预期用途,但我发现它经常被误解、滥用和虐待,所以简单来说,它是危险的。 - CB Bailey
6
我认为strncpy的“预期用途”大多已经过时,这个概念不再有用... - R.. GitHub STOP HELPING ICE

4

printf/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 

6
这个答案充满了错误的建议和误解。绝对没有理由不在生产代码中使用stdio,实际上我认为snprintf是你应该在处理字符串时使用的 唯一 标准库字符串函数,尤其是在安全意识较高的代码中使用。性能成本被高度夸大(除了常数开销时间之外,它不应该比strlen/memcpy更慢,很可能内部使用这些函数),危险也被夸大了(任何现代编译器都会通过警告提示格式字符串不匹配)。 - R.. GitHub STOP HELPING ICE
关于安全和生产代码,我引用了一个非常著名且广泛认可的权威机构来支持关键应用程序,即MISRA-C:2004规则20.9。如果您持有不同意见,作为一个随机的互联网用户,您的主观看法并没有太多的分量,请引用另一个支持您观点的权威机构。 - Lundin
1
而且你的测量结果显示成本最小,正如预期的那样。唯一的原因是,由于你的字符串太短,所以成本看起来很不可行。如果你尝试使用1k或20k的字符串,你会发现性能几乎相同。 - R.. GitHub STOP HELPING ICE
1
@Lundin:我不是在谈论编写自己的strcpy。我是在谈论通过将多个字符串或数字输入与字符串组合来组装字符串。当然,您可以使用strlenmemcpy自己完成它,但每次这样做时,都存在犯错误(偏移一个字符,拼写错误等)的非微不足道的概率,如果您使用标准的安全习惯用法,则不存在这些错误。此外,与strlenmemcpy调用的字符串相比,使用snprintf通常使调用代码更小。 - R.. GitHub STOP HELPING ICE
3
对于至少尝试解释事实并花费大量时间进行评论的做法,我给予+1的赞赏。 - Vijay
显示剩余7条评论

1
我不会仅仅为了复制一个字符串而使用sprintf。这太过于浪费,读到这段代码的人肯定会停下来想知道我为什么这样做,以及是否有什么遗漏。

0

有一种使用sprintf()(或者如果你很谨慎,可以使用snprintf())来进行“安全”字符串复制的方法,它会截断而不是溢出字段或使其未被NUL终止。

这就是使用“*”格式字符作为“字符串精度”的方法:

所以:

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

这将未知字符串的内容放入dest_buff中,为终止NUL留出空间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接