为啥要使用 strncpy 而不是 strcpy?

Posted

技术标签:

【中文标题】为啥要使用 strncpy 而不是 strcpy?【英文标题】:Why should you use strncpy instead of strcpy?为什么要使用 strncpy 而不是 strcpy? 【发布时间】:2010-11-18 12:23:33 【问题描述】:

编辑:我已经添加了示例的源代码。

我遇到了this example:

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 );

产生了这个输出:

destination 原来是 = 'abcdefg'
strcpy 后,destination 变为 '123456789'

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

这让我想知道为什么有人会想要这种效果。看起来会很混乱。这个程序让我觉得你基本上可以用 Tom Bro763 复制某人的名字(例如 Tom Brokaw)。

使用strncpy()优于strcpy()有什么优势?

【问题讨论】:

我想你的意思是问“为什么会有人使用strcpy 而不是strncpy?” 当我是第一学期 C 编程课程的助教时,我向我的学生保证,当我根据精心设计的输入对它们进行评分时,任何使用像 getline 这样的方法都会导致错误的结果。 :) 真的很遗憾 C 从来没有一个像样的字符串标准库。 这没什么好可惜的。我的意思是,它完全打破了我,让高级语言变得更有趣:) My rant on the topic of strncpy() 【参考方案1】:

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

这就是strncpy() 的两个奇怪背后的原因:

如果目的地完全填满,它不会在目的地上放置一个空终止符;和 它总是完全填充目的地,必要时使用 nuls。

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

if (dest_size > 0)

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

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

【讨论】:

但是,当然,strncpy 也不是你想要的:strncpy 接受到 addnot 目标的最大字符数缓冲区大小...但这只是一件小事,因此除非您尝试将一个字符串连接到另一个字符串,否则可能不会成为问题。 我不知道它的原因,这与我在atm上的工作非常相关。 strncpy() 函数旨在以固定长度的空填充格式存储字符串。这种格式用于原始 Unix 目录条目,但也用于无数其他地方,因为它允许将 0-N 字节的字符串存储在 N 字节的存储空间中。即使在今天,许多数据库在其固定长度的字符串字段中使用空填充字符串。与 strncpy() 的混淆源于它将字符串转换为 FLNP 格式的事实。如果需要的是一个 FLNP 字符串,那就太好了。如果需要一个以 null 结尾的字符串,则必须自己提供终止符。 为什么我们需要在 strncat 调用之前写dest[0] = '\0';?先生介意解释一下吗? @snr: strncat() 将源字符串连接到目标字符串的末尾。我们只想将源字符串复制到目标,所以我们首先将目标设置为空字符串——这就是dest[0] = '\0'; 所做的。【参考方案2】:

strncpy 通过要求您在其中输入一个长度来对抗缓冲区溢出。 strcpy 取决于尾随 \0,这可能并不总是出现。

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

n 函数都用作针对缓冲区溢出的防御性编码。请使用它们代替旧函数,例如strcpy

【讨论】:

参见lysator.liu.se/c/rat/d11.htmlstrncpy 最初被引入到 C 库中,用于处理目录条目等结构中的固定长度名称字段。此类字段的使用方式与字符串不同:对于最大长度字段,尾随 null 是不必要的,并且将较短名称的尾随字节设置为 null 可确保有效的逐字段比较。 strncpy 并非起源于“有界 strcpy”,委员会更愿意承认现有做法,而不是改变功能以更好地适应这种用途。 我不知道为什么这会得到很多赞成票 - strncpy 从来没有打算作为 strcpy 的更安全的替代品,事实上它并没有更安全,因为它不会零终止字符串。它还具有不同的功能,因为它用 NUL 字符填充提供的长度。正如 caf 在他的回复中所说 - 它用于覆盖固定大小数组中的字符串。 事实上,strncpy不是strcpy 的更安全版本。 @Sinan:我从来没有说过它更安全。是防御性的。它迫使你投入一个长度,因此让你思考你在做什么。有更好的解决方案,但事实仍然是人们会(并且确实)使用strncpy 而不是strcpy,因为它是一种更具防御性的功能......这就是我所说的。 n 个函数都用作针对缓冲区溢出的防御性编码。请使用它们来代替旧函数,例如 strcpy。 这适用于 snprintf,但与 strncat 无关,对于 strncpy 则完全不适用。这个答案怎么会得到如此多的支持?它显示了这种虚假功能的情况有多糟糕。使用它不是防御性的:在大多数情况下,程序员不理解它的语义并创建一个潜在的非零终止字符串。【参考方案3】:

虽然我知道strncpy 背后的意图,但它并不是一个真正好的功能。避免两者。 Raymond Chen explains.

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

另见Why is strncpy insecure?

【讨论】:

【参考方案4】:

strncpy 并不比 strcpy 更安全,它只是将一种错误与另一种错误交换。在 C 中,处理 C 字符串时,您需要知道缓冲区的大小,没有办法绕过它。 strncpy 对于其他人提到的目录是合理的,但否则,你不应该使用它:

如果您知道字符串和缓冲区的长度,为什么要使用 strncpy ?充其量就是浪费算力(加了无用的0) 如果您不知道长度,那么您可能会默默地截断您的字符串,这并不比缓冲区溢出好多少

【讨论】:

我认为这是对strncpy的一个很好的描述,所以我投了赞成票。 strncpy 有它自己的一套麻烦。我想这就是例如 glib 有它自己的扩展的原因。是的,不幸的是你作为程序员必须知道所有数组的大小。将 0 终止的 char 数组作为字符串的决定让我们付出了沉重的代价.... 零填充字符串在以固定格式文件存储数据时非常常见。可以肯定的是,数据库引擎和 XML 之类的东西的流行,以及不断变化的用户期望,已经导致固定格式文件不像 20 年前那样普遍。尽管如此,此类文件通常是最省时的数据存储方式。除非记录中数据的预期长度和最大长度之间存在巨大差异,否则将记录作为包含一些未使用数据的单个块读取比读取分成多个块的记录要快得多。 刚刚接管了遗留代码的维护,它使用了 g_strlcpy(),因此不会遭受填充效率低下的问题,但果然,传输的字节数没有被维护,所以代码默默地截断了结果。【参考方案5】:

您正在寻找的是函数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;

【讨论】:

您可以写到 strlcpy 在 Linux 和 Windows 以外的几乎所有设备上都可用!但是,它是 BSD 许可的,因此您可以将其放入您的库之一并从那里使用它。 您可能想为dstsize &gt; 0 添加一个测试,如果不是,则什么也不做。 是的,你是对的。我将添加检查,因为没有它,dstsize 将触发目标缓冲区上长度为 lenmemcpy 并溢出它。 加一个用于推广好的解决方案。更多人需要了解 strlcpy,因为每个人都在不断地改造它。 @MichaelvanderWesthuizen 它在 Linux 上可用,只是在 glibc 中不可用。查看我的答案以获取更多信息(1)(2)(3)【参考方案6】:

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

对于像 read() 这样的 POSIX API 函数,它不会将终止的 0 放入缓冲区,而是返回读取的字节数,您可以手动放入 0,或者使用 strncpy() 复制它。

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

【讨论】:

【参考方案7】:

strncpy 用 '\0' 填充目标源的大小,即使目标的大小更小....

手册页:

如果 src 的长度小于 n,strncpy() 填充剩余的 dest 字节为空。

不仅是余数……在这之后直到 n 个字符是 到达。因此你得到一个溢出......(参见手册页 实施)

【讨论】:

strncpy 用 '\0' 填充目标源的大小,即使目标的大小更小.... 恐怕这句话是错误且令人困惑:strncpy 如果源的长度更短,则使用 '\0' 作为大小参数填充目标。 size 参数不是源的大小,也不是从源复制的最大字符数,就像在 strncat 中一样,它是目标的大小。 @chqrlie:没错。 strncpy 优于其他复制操作的一个优点是它保证将写入整个目标。由于编译器在复制包含某些不确定值的结构时可能会尝试获得“创造性”,因此确保结构中的任何字符数组都被完全写入可能是防止“意外”的最简单方法。 @supercat:对于这种特定情况来说是一个非常小的优势......但必须在调用 strncpy 后修补目标以确保空终止:strncpy(dest, src, dest_size)[dest_size - 1] = '\0'; @chqrlie:是否需要尾随空字节取决于数据应该表示的内容。在结构中使用零填充而不是零终止的数据并不像以前那样普遍,但如果例如目标文件格式使用 8 字节的部分名称,能够在结构中使用 char[8] 处理最多 8 个字符可能比使用 char[8] 更好,但只能处理 7 个字符,或者必须将字符串复制到char[9] 缓冲区,然后将memcpy 复制到目的地。 @chqrlie:大多数处理字符串的代码应该知道它们可能有多长,并且不应该盲目地使用char 指针运行,直到它们达到零。 only 以零结尾的字符串真正适合的是字符串文字,即使存在可变长度编码的前缀也可能会更好。对于几乎所有其他内容,最好让字符串以长度为前缀 具有特殊前缀,这表明char* 确实类似于struct stringInfo char header[4]; char *realData; size_t length; size_t size;【参考方案8】:

这可以用于许多其他场景,您只需将原始字符串的一部分复制到目的地。使用 strncpy(),您可以复制原始字符串的有限部分,而不是使用 strcpy()。我看到你提交的代码来自publib.boulder.ibm.com。

【讨论】:

【参考方案9】:

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

每当我们不想复制整个字符串或只想复制 n 个字符时,我们都会使用 strncpy。但是 strcpy 会复制整个字符串,包括终止空字符。

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

about strcpy

about strncpy

【讨论】:

【参考方案10】:

strncpy 是 strcpy 的一个更安全的版本,事实上你不应该使用 strcpy,因为它潜在的缓冲区溢出漏洞使你的系统容易受到各种攻击

【讨论】:

请参阅lysator.liu.se/c/rat/d11.html:strncpy 函数 strncpy 最初被引入 C 库,用于处理结构中的固定长度名称字段,例如目录条目。此类字段的使用方式与字符串不同:对于最大长度字段,尾随 null 是不必要的,并且将较短名称的尾随字节设置为 null 可确保有效的逐字段比较。 strncpy 并不是“有界 strcpy”的起源,委员会更愿意承认现有的做法,而不是改变功能以更好地适应这种用途。

以上是关于为啥要使用 strncpy 而不是 strcpy?的主要内容,如果未能解决你的问题,请参考以下文章

C++笔记--strcpy, strncpy, memcpy(16)

C++笔记--strcpy, strncpy, memcpy(16)

strncpy()函数的功能

strncpy()函数的功能

strcpy和strncpy

strcpy()strncpy()和memcpy()对比