在我的情况下,应该使用strncpy还是strlcpy?

25

我想要将src_str复制到dst_arr,应该使用什么方法?为什么?

char dst_arr[10];
char *src_str = "hello";

注意:我知道strlcpy并非所有地方都可用,这不是问题所在。

PS:读了很多关于strncpystrlcpy好坏的东西后,我的头比电脑硬盘转得更快了。

6个回答

38
当目标字符串以零结尾时,strncpy从不是正确的答案。 strncpy 是一种用于处理非终止的固定宽度字符串的函数。更准确地说,它的目的是通过复制将零结尾的字符串转换为非终止的固定宽度字符串。换句话说,在这里使用strncpy没有意义。
你所面临的真正选择是在strlcpy和普通的strcpy之间。
当您要对dst_arr执行“安全”(即可能被截断)的复制时,应使用适当的函数strlcpy
至于dst_ptr......不存在“复制到dst_ptr”。您可以将其复制到由dst_ptr指向的内存,但首先必须确保它指向某个位置并分配该内存。有许多不同的方法来实现这一点。
例如,您可以使dst_ptr指向dst_arr,在这种情况下,答案与前一个例子相同-strlcpy
或者您可以使用malloc来分配内存。如果分配的内存量保证足以容纳字符串(即分配了至少strlen(src_str)+1字节),则可以使用普通的strcpy甚至memcpy来复制字符串。在这种情况下,没有必要也没有理由使用strlcpy,尽管有些人可能更喜欢使用它,因为它给他们一种额外安全感的感觉。
如果您故意分配较少的内存(即希望截断字符串),则strlcpy成为合适的函数。

1
谢谢。当你说“在使用以零结尾的字符串时,strncpy永远不是正确的答案”时,你是指源字符串还是目标字符串? - hari
2
@hari:目标字符串。strncpy是一种转换函数,它将以零结尾的源字符串转换为非终止的固定宽度目标字符串。 - AnT stands with Russia
所以,如果我的目标字符串是char dst_arr[10];,它没有以空字符结尾。那么我应该使用strlcpy对吗? - hari
2
@hari:嗯...我不明白这个问题。你的dst_arr最初是什么并不重要,它最初只是一块未初始化的原始内存块。问题是你想在dst_arr中创建什么。我假设你想在dst_arr中创建一个标准的普通零结尾字符串(因为你说你想将一个普通的零结尾字符串复制到它中)。如果是这样,请使用strlcpy。如果你想在dst_arr中创建一个“固定宽度字符串”(这相当不寻常,因为大多数时候现在没有人使用固定宽度字符串),请使用strncpy - AnT stands with Russia
1
我想补充一下,strncpy 不需要源字符串以 null 结尾。如果在 n 个字符中没有找到 null,则它会停止查找。但是,如果在前 n 个字符中找到 null,则将其视为终止符。与 strlcpy 不同,后者总是需要在 s 的末尾找到 null,以便可以返回 strlen(s) - greggo
显示剩余2条评论

7

strlcpy()比起strncpy()更加安全,所以你最好使用它。
那些没有strlcpy()的系统通常会有一个执行相同功能的s_strncpy()

注意:在指向某个东西之前,你不能将任何内容复制到dst_ptr中。


2
这样做有一定的安全性,但并不完全安全。例如,strlcpy会截断源字符串以适应目标字符串,这是一种安全风险。 - rurban

4
我不知道strlcpy。我在这里发现:这里:
strlcpy()和strlcat()函数分别复制和连接字符串。它们旨在成为strncpy(3)和strncat(3)的更安全、更一致和更少出错的替代品。
所以,strlcpy似乎更安全。
编辑:完整的讨论可以在这里找到。
编辑2:
我意识到我上面写的没有回答“在你的情况下”的问题部分。如果你了解strncpy的限制,我想你可以使用它并编写良好的代码来避免其陷阱; 但是,如果您对其限制的理解不确定,请使用strlcpy。
我对strncpy和strlcpy的限制的理解是,您可以使用strncpy做一些非常糟糕的事情(缓冲区溢出),而您可以在strlcpy中失去一个字符。

谢谢。因此,在使用strlcpy时,你需要注意的唯一一件事就是,你应该为目标字符串提供(src_length + 1 for null)个字节的空间。正确吗? - hari
并不完全正确。最重要的是在第三个参数中给出最大的dst大小,以避免缓冲区溢出。从那里开始,如果这个第三个参数至少是src_length + 1,则在复制过程中不会丢失任何字符。也许您希望截断src,如果第三个参数小于src_length + 1,则会发生这种情况。但是要小心,要将str截断为5个字符,第三个参数应该是6,并且dst大小必须至少为6,以避免缓冲区溢出。 - jfg956
是的,要复制一个长度为len个字符的字符串(例如:"hello"的大小为5),您需要一个长度为len+1个字符的缓冲区。 - jfg956
4
不,那是错误的。strlcpy 的目的是避免输出缓冲区溢出。这意味着 size 参数是目标缓冲区的大小,而不是源缓冲区...确切地说,它是目标缓冲区中剩余的空间量。如果你"始终提供 src_length+1",你的代码将充满缓冲区溢出漏洞。 - Jim Balter
Jim Balter:明白了。我从@AndreyT的回复中意识到了这一点。无论如何,还是感谢您的建议。 - hari
显示剩余3条评论

1

在这种情况下,您应该始终使用标准函数,即C11的strcpy_s()函数。不要使用strncpy(),因为它不安全且不能保证零终止。也不要使用仅适用于OpenBSD的strlcpy(),因为它也不安全,而且OpenBSD总是使用自己的发明,通常不会进入任何标准。

See http://en.cppreference.com/w/c/string/byte/strcpy

函数strcpy_s类似于BSD函数strlcpy,但是strlcpy会截断源字符串以适合目标字符串(这是一种安全风险)。

  • strlcpy未执行strcpy_s所执行的所有运行时检查
  • strlcpy在调用失败时不会将目标设置为空字符串或调用处理程序,因此无法明显地表明失败。
  • 尽管strcpy_s由于潜在的安全风险而禁止截断,但可以使用边界检查的strncpy_s来截断字符串。

如果您的C库没有strcpy_s,请使用safec lib。 https://rurban.github.io/safeclib/doc/safec-3.1/df/d8e/strcpy__s_8c.html


-1

首先,您的dst_ptr没有分配空间,也没有设置它指向其他位置,因此将任何内容分配给它可能会导致段错误。

Strncpy应该可以正常工作 - 只需执行以下操作:

strncpy(dst_arr, src_str, sizeof(dst_arr));

你知道你不会溢出dst_arr。如果你使用更大的src_str,你可能需要在dst_arr的末尾放置自己的空终止符,但在这种情况下,你的源是<你的目标,所以它将被填充为null。

这在任何地方都有效且安全,所以除非出于学术好奇心,否则我不会考虑其他任何东西。

还要注意最好使用非魔术数字10,以便你知道其大小与strncpy的大小匹配 :)


strncpy几乎从来都不是正确的选择,因为它会填充零值但不会添加零终止符。提问者想要使用strlcpy,它会添加零终止符而不进行零填充。此外,谈论“在这种情况下”是一个错误,因为这个问题只是一个例子...真实的代码不会将一个固定的字符串(如"hello")复制到一个已知更大的固定大小缓冲区中。 - Jim Balter

-1

你不应该使用strncpy和strlcpy来完成这个任务。最好使用其他方法。

*dst_arr=0; strncat(dst_arr,src_arr,(sizeof dst_arr)-1);

或者没有初始化

sprintf(dst_arr,"%.*s",(sizeof dst_arr)-1,src_arr);

这里的dst_arr必须是一个数组,而不是一个指针。


2
strlcpy 可以用于 OP 的需求。 - Jim Balter
sizeof(int)sizeof(size_t)不同时,sprintf(dst_arr,"%.*s",(sizeof dst_arr)-1,src_arr);是未定义的行为。printf(dst_arr,"%.*s",(int) (sizeof dst_arr - 1),src_arr);是朝着正确方向迈出的一步。 - chux - Reinstate Monica

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