为什么要使用strncpy而不是strcpy?


83

编辑:我已经为示例添加了源。

我遇到了这个例子

char source[MAX] = "123456789";
char source1[MAX] = "123456789";
char destination[MAX] = "abcdefg";
char destination1[MAX] = "abcdefg";
char *return_string;
int index = 5;

/* This is how strcpy works */
printf("destination is originally = '%s'\n", destination);
return_string = strcpy(destination, source);
printf("after strcpy, dest becomes '%s'\n\n", destination);

/* This is how strncpy works */
printf( "destination1 is originally = '%s'\n", destination1 );
return_string = strncpy( destination1, source1, index );
printf( "After strncpy, destination1 becomes '%s'\n", destination1 );

产生此输出的内容:

目的地最初是='abcdefg'
在strcpy之后,目标变为“ 123456789”

destination1最初是='abcdefg'
在strncpy之后,destination1变为'12345fg'

这使我想知道为什么有人会想要这种效果。看起来会令人困惑。该程序使我认为您基本上可以使用Tom Bro763复制某人的名字(例如Tom Brokaw)。

使用 strncpy() over有 strcpy()什么优点


81
我认为您是想问“为什么有人会strcpy代替strncpy?”
山姆·哈威尔

5
当我是用C开设的第一学期编程课程的助教时,我向学生保证,getline如果我根据精心设计的输入对它们进行评分,则任何类似方法的使用都会导致错误的结果。:)
山姆·哈威尔

4
我认为您误解了代码的实际作用。细看。
Emil H,2009年

6
真可惜C从来没有一个像样的标准字符串库。
starblue

7
可惜的不是什么。我的意思是,这完全让我感到困惑,并使高级语言更加有趣:)
卡森·迈尔斯

Answers:


98

strncpy通过要求您增加长度来防止缓冲区溢出。strcpy取决于尾随\0,可能并不总是发生。

其次,为什么您选择只在7个字符串上复制5个字符,这超出了我的范围,但是却产生了预期的行为。它仅复制第一个n字符,n第三个参数在哪里。

这些n功能均用作防御缓冲区溢出的防御性编码。请使用它们代替较早的功能,例如strcpy


47
参见lysator.liu.se/c/rat/d11.htmlstrncpy最初被引入C库,以处理诸如目录条目之类的结构中的定长名称字段。此类字段的使用方式与字符串不同:对于最大长度的字段,不需要尾随null;将较短名称的尾随字节设置为null可以确保按字段进行有效的比较。 strncpy并非源于“有约束力的斗争”,委员会宁愿承认现有做法,也不愿改变职能使其更适合这种使用。
锡南·努尔

35
我不确定为什么这会引起很多投票-strncpy从来没有打算作为strcpy的更安全的选择,并且实际上没有任何安全性,因为它不会将字符串终止为零。它还具有不同的功能,因为它用NUL字符填充了所提供的长度。正如caf在他的答复中所说-用于覆盖固定大小的数组中的字符串。
机油尺

26
但事实上,strncpy不是一个更安全的版本strcpy
锡南·努尔

7
@Sinan:我从来没有说过它更安全。是防御性的。它迫使您付出一定的努力,使您思考自己在做什么。有更好的解决方案,但事实仍然是人们会(并且确实)使用它,strncpy而不是strcpy因为它具有更多防御功能……这就是我所说的。
Eric

9
n个函数均用作防御缓冲区溢出的防御性编码。请使用它们代替旧功能,例如strcpy。这是正确的snprintf,但无关紧要strncat,完全不正确strncpy。这个答案怎么可能获得如此多的赞誉?它显示了关于此伪功能的情况有多糟糕。使用它不是防御性的:在大多数情况下,程序员不了解其语义,并创建了一个可能为非零终止的字符串。
chqrlie

178

strncpy()设计该函数时要考虑一个非常特殊的问题:处理以原始UNIX目录条目的方式存储的字符串。它们使用固定大小的数组,并且仅当文件名短于数组时才使用nul终止符。

这就是两个奇怪之处的背后strncpy()

  • 如果完全填满,它不会在目的地放置nul终止符;和
  • 它始终完全填充目的地,必要时使用零食。

为了“安全strcpy()”,最strncat()好像这样使用:

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

这将始终终止结果,并且不会复制过多的内容。


但是,当然,strncpy也不总是您想要的:strncpy接受要添加的最大字符数,而不是目标缓冲区的大小……但这只是一件小事,因此除非您没有,否则可能不会成为问题正在尝试将一个字符串连接到另一个字符串。
David Wolever 09年

我不知道它的原因,这与我在atm上进行的工作非常相关。
马特·乔纳

strncpy()函数旨在以固定长度的null填充格式存储字符串。这种格式曾用于原始Unix目录条目,但也用于其他许多地方,因为它允许将0-N字节的字符串存储在N字节的存储空间中。即使在今天,许多数据库在其固定长度字符串字段中仍使用填充了空值的字符串。与strncpy()的混淆源于将字符串转换为FLNP格式的事实。如果需要的是FLNP字符串,那就太好了。如果需要一个以零结尾的字符串,则必须自己提供终止符。
supercat

1
为什么我们需要dest[0] = '\0';在strncat调用之前写?您介意先生吗?
snr

4
@snr:strncat()将源字符串连接到目标字符串的末尾。我们只想将源字符串复制到目标字符串,所以我们首先将目标字符串设置为空字符串-就是dest[0] = '\0';这样。
caf

34

虽然我知道背后的意图strncpy,但这并不是一个很好的功能。避免两者。Raymond Chen解释

就个人而言,strncpy如果要处理以null结尾的字符串,我的结论只是避免与它的所有朋友成为朋友。尽管名称中带有“ str”,但这些函数不会产生以null终止的字符串。他们将以空值结尾的字符串转换为原始字符缓冲区。在需要以空值结尾的字符串作为第二个缓冲区的地方使用它们是完全错误的。如果源太长,不仅无法获得正确的null终止,而且如果源很短,则会获得不必要的null填充。

另请参阅为什么strncpy不安全?


27

strncpy并不比strcpy安全,它只是将一种错误与另一种错误进行了交换。在C语言中,当处理C字符串时,您需要知道缓冲区的大小,无法解决它。strncpy对于其他人提到的目录内容是合理的,但是否则,您永远不要使用它:

  • 如果知道字符串和缓冲区的长度,为什么要使用strncpy?充其量是在浪费计算能力(添加无用的0)
  • 如果您不知道长度,那么您可能会面临无提示截断字符串的风险,这并不比缓冲区溢出好多少

我认为这是对strncpy的很好描述,所以我投票赞成。strncpy有它自己的麻烦。我想这就是例如glib拥有自己的扩展的原因。是的,不幸的是,作为程序员,您必须了解所有数组的大小。以0终止的char数组作为字符串的决定使我们所有人付出了沉重的代价..
Friedrich

1
将数据存储在固定格式的文件中时,零填充字符串很常见。可以肯定的是,诸如数据库引擎和XML之类的东西的普及以及不断发展的用户期望,已经导致固定格式的文件比20年前少见。但是,此类文件通常是最节省时间的数据存储方式。除非记录中数据的预期长度和最大长度之间存在巨大差异,否则将记录作为包含一些未使用数据的单个块读取要比读取分为多个块的记录要快得多。
supercat

只需接管了使用g_strlcpy()的遗留代码的维护,就不会遇到填充效率低下的问题,但是可以肯定的是,传输的字节数没有得到维护,因此代码默默地截断了结果。
user2548100 2014年

21

您正在寻找的功能strlcpy()是始终以0终止字符串并初始化缓冲区。它还能够检测溢出。唯一的问题,它(不是)真正可移植的,仅在某些系统(BSD,Solaris)上存在。此功能的问题在于,它打开了另一种蠕虫,如http://en.wikipedia.org/wiki/Strlcpy上的讨论所见。

我个人的观点是,它比strncpy()和更有用strcpy()。它具有更好的性能,并且是的良好伴侣snprintf()。对于没有它的平台,它相对容易实现。(在应用程序的开发阶段,我用陷阱版本代替了这两个功能(snprintf()strlcpy()),该版本会在缓冲区溢出或截断时残酷地中止程序。这可以迅速捕获最严重的违规者。尤其是当您在其他人的代码库中工作时。

编辑:strlcpy()可以轻松实现:

size_t strlcpy(char *dst, const char *src, size_t dstsize)
{
  size_t len = strlen(src);
  if(dstsize) {
    size_t bl = (len < dstsize-1 ? len : dstsize-1);
    ((char*)memcpy(dst, src, bl))[bl] = 0;
  }
  return len;
}

3
您可以写道,strlcpy在Linux和Windows之外的几乎所有其他功能上都可用!但是,它已获得BSD许可,因此您可以将其放入一个库中并从那里使用它。
Michael van der Westhuizen,2009年

您可能想要添加一个测试,dstsize > 0如果没有,则不执行任何操作。
chqrlie

你是对的。我将添加检查,因为没有它,dstsize将触发目标缓冲区memcpy的lengthlen并使它溢出。
PatrickSchlüter'16

再加上一个促进好的解决方案。更多的人需要了解strlcpy,因为每个人都在不断地对其进行改造。
rsp

@MichaelvanderWesthuizen它在Linux上可用,只是在glibc中不可用。请参阅我的答案以获取更多信息(1) (2) (3)
rsp

3

strncpy()功能比较安全:您必须传递目标缓冲区可以接受的最大长度。否则,可能会发生源字符串未正确终止为0的情况,在这种情况下,该strcpy()函数可能会向目标写入更多字符,从而破坏目标缓冲区之后的内存中的任何内容。这是许多漏洞利用中使用的缓冲区溢出问题

同样对于POSIX API函数(例如read(),不会将终止0放入缓冲区,但返回读取的字节数),您可以手动将0放入,或使用进行复制strncpy()

在您的示例代码中,index实际上不是索引,而是count-,它指示从源到目标最多复制多少个字符。如果源的前n个字节中没有空字节,则放置在目标中的字符串不会以空终止


1

即使目标的大小较小,strncpy也会用“ \ 0”填充目标的大小,即使目标的大小较小。

手册页:

如果src的长度小于n,则strncpy()将空字节填充dest的其余部分。

不仅是剩余的...在此之后直到达到n个字符。因此,您将产生溢出...(请参见手册页的实现)


3
strncpy()函数填充目的地了“\ 0”源的大小沉绵目的地的尺寸越小.... 恐怕这种说法是错误的,混淆:strncpy用“\ 0”为填充了目的地size参数,如果源的长度较小。size参数不是源的大小,不是从源复制的最大字符数,因为它在中strncat,它是目标的大小。
chqrlie

@chqrlie:是的。strncpy与其他复制操作相比,它的优点是可以保证将写入整个目标。由于编译器在复制包含一些不确定值的结构时可能会尝试获得“创意”,因此确保结构中的任何字符数组都被完整写入可能是防止“意外”的最简单方法。
超级猫

@supercat:对于这种特定情况而言,这是一个非常小的优势...但是必须在调用后修补目标strncpy以确保终止为空: strncpy(dest, src, dest_size)[dest_size - 1] = '\0';
chqrlie

@chqrlie:是否需要尾随空字节取决于数据应表示的内容。在结构中使用填充零而不是零终止的数据并不像以前那样普遍,但是,例如,如果对象文件格式使用8字节的节名,则能够char[8]在结构中使用a来处理事情到8个字符可能比使用a更好,char[8]但只能处理7个字符,或者必须将字符串复制到char[9]缓冲区中,然后再将memcpy其复制 到目标位置。
超级猫

@chqrlie:大多数处理字符串的代码都应该知道它们可能持续多长时间,并且char在出现零之前,不要盲目地使用指针。的唯一的事情零结尾的字符串是为字符串文字真的很不错,甚至有一个可变长度编码的前缀,可能会更好。对于几乎所有其他内容,最好让字符串带有一个长度前缀一个特殊的前缀,以表明char*确实是struct stringInfo {char header[4]; char *realData; size_t length; size_t size;}
超级猫

-1

这可能会在许多其他情况下使用,您只需要将原始字符串的一部分复制到目标即可。使用strncpy()可以复制原始字符串的有限部分,而不是strcpy()。我看到您输入的代码来自publib.boulder.ibm.com


-1

那取决于我们的要求。对于Windows用户

每当我们不希望复制整个字符串或仅希望复制n个字符时,就使用strncpy。但是strcpy复制整个字符串,包括终止空字符。

这些链接将帮助您更多地了解strcpy和strncpy以及我们可以在哪里使用。

关于strcpy

关于strncpy


-8

实际上,strncpy是strcpy的安全版本,绝对不要使用strcpy,因为它可能存在缓冲区溢出漏洞,使您的系统容易受到各种攻击


6
参见lysator.liu.se/c/rat/d11.html:strncpy函数strncpy最初被引入C库中,以处理诸如目录条目之类的结构中的定长名称字段。此类字段的使用方式与字符串不同:对于最大长度的字段,不需要尾随null;将较短名称的尾随字节设置为null可以确保按字段进行有效的比较。strncpy并非源于``有约束力的strcpy'',委员会更愿意承认现有做法,而不是改变职能以使其更适合这种使用。
锡南·努尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.