strncpy文档问题

6

5
简而言之,避免使用strncpy作为安全的strcpy,它是一种不同目的的函数,容易导致其他安全风险。 - Matteo Italia
这个标记为C++是有意为之吗?如果你正在使用C ++,我建议完全避免使用C字符串函数,而是使用C ++ Std :: String函数。否则,我建议删除C ++标记... - James Greenhalgh
我不同意移除标签@James,尽管我赞同您使用“正确”的字符串的建议。然而,虽然大多数情况下使用C++字符串可能更好,但仍有一些情况下旧的C字符串可能很有用,并且它们毕竟是C++标准的一部分。 - paxdiablo
3
cplusplus.com是一个充斥着措辞不当、误导性强,甚至常常有错误信息的网站。我建议避免使用该网站,最好阅读相关的标准文档。 - R.. GitHub STOP HELPING ICE
1
我也不推荐cplusplus.com。对于任何C语言相关的内容,建议优先查找opengroup.org的搜索结果而非cplusplus.com。以这个例子为比较,cplusplus.com忽略了在源和目标重叠时行为是未定义的事实。Opengroup的文本来自Posix并且基本上复制了C标准。对于C++,很难找到好的网页形式的信息,因此可以通过网络搜索需要的类/函数名称,然后在书籍或标准的PDF中查找。 - Steve Jessop
@James。是的,实际上故意使用了C++标签。 - Simplicity
6个回答

13

这意味着,例如,如果您的源字符串是20个字符加上一个空终止符,而您的strncpy指定少于21个字符,则目标字符串将不会附加空终止符。

这是因为它的工作方式:strncpy保证将写入确切的N个字节,其中N是传递的长度值。

如果源字符串(不包括空字节)的长度小于该值,则它将用null填充目标区域。如果它等于或大于该值,则不会在目标中添加null。

这意味着您可能得到的不是严格意义上的C字符串。可以通过像这样的代码解决:

char d[11];          // Have enough room for string and null.
strncpy (d, s, 10);  // Copy up to 10 bytes of string, null the rest.
d[10] = '\0';        // Then append null manually in case s was too long.

你分配了11个字节(数组索引为0..10),最多复制10个(索引为0..9),然后将第11个(索引为10)设置为null。

这里是一个图示,展示了使用strncpy (d,s,10)将各种大小的字符串写入10个字符区域的三种可能性,其中.表示空字节:

s              d
-------------  ----------
Hello.         Hello.....
Hello Fred.    Hello Fred
Hello George.  Hello Geor

请注意,在第二和第三种情况下,没有写入null字节,因此,如果您将d视为字符串,则可能会对结果感到失望。


3
"你可能对结果感到失望" - 是的,为什么UB总是愚蠢的鼻妖怪,而不是比如一个蛋糕呢?一个非鼻子的蛋糕,就是这样。" - Steve Jessop
@Steve:哈哈,好笑。实际上,这可能是一个完整问题的基础(尽管即使作为 CW,它也可能会被关闭):“有人经历过好的未定义行为吗?不仅仅是它做了你期望的事情,而是它超越了职责范围。” - paxdiablo
@Steve,即使UB制作蛋糕,它也往往带有恶魔的味道,我很抱歉。 - bdonlan
@bdonlan: 或者带有鼻音味道的。 - Steve Jessop
@paxdiablo。感谢您的回复。您能解释一下这行代码“d[10] ='\0';”吗?特别是当“s”太长时,“10”位置已经填满了怎么办?如何添加一个“null”呢?谢谢。 - Simplicity
@SWEngineer:在这种情况下,d[10] 实际上 不会 被填充。复制 10 个字节将填充 d[0]d[9](包括),留出 d[10] 用作 null。这就是为什么要声明它为 int d[11]010)。更新回答以澄清。 - paxdiablo

5
字符串"foo"有3个字符+1个空终止符(存储为"foo\0"),总长度为4。如果你使用n=3(或更少)调用strncpy,它将不会在目标字符串的末尾附加一个空终止符,只会复制"foo"。由于缺少标志字符串结束的空终止符,尝试打印结果字符串将导致未定义的行为。
你必须非常小心处理这种情况,要么将n传递为源字符串中字符的最大值加1,要么自己添加空终止符。

4
在C语言中,字符串存储为char数组,它们以空字符结尾,也就是说,在字符串的末尾添加了一个额外的0,用于标记字符串的结尾,并且可以在后面用来确定字符串的长度。因此,字符串"hello"在内存中的样子如下:
char hello[] = {'h', 'e', 'l', 'l', 'o', 0};

通常情况下,当你复制一个字符串时,null字符也应该被复制。因此,字符串缓冲区所需的内存为其长度加1(如:(strlen(hello) + 1) * sizeof(char))。

strncpy函数允许你仅复制能够适合提供的缓冲区中的字符数量。如果你提供的缓冲区不足以容纳这个额外的null,它将不会被添加。或者,如果字符串被截断,则不会以null结尾。

char hello[] = "hello"; // 5 characters, 6 bytes long
char hel[3];
strncpy(hel, hello, 3); // hel is {'h', 'e', 'l'}

在调用strncpy后,您应该始终小心,因为结果可能不是有效的C字符串。如果字符串没有以空字符结尾,那么它的长度是无法确定的,大多数字符串操作函数将失败或执行意外操作。


请不要使用 * sizeof(char),这会增加您键盘的按键次数和显示器所需的电子数量,从而缩短它们的寿命 :-) - paxdiablo
好的,也许应该是 sizeof(hello[0]) - detunized

4
这意味着它会复制源字符串的终止空字符,但如果源字符串不适合目标位置,则不会添加终止空字符。

1
这意味着只有从源缓冲区到目标缓冲区复制 num 字节,因此如果源字符串长度大于或等于 num,则终止的 NULL 字节将不会被复制,结果将不包括 NULL 结尾字节,这是危险的。
建议使用 strlcpy

推荐者是谁?肯定不是glibc的人们 :-) 我的建议是要学习你正在使用的工具的所有怪癖,而不是依赖辅助工具。那些想要这种安全级别的人应该离开美妙的C语言世界,回到Visual Basic :-) 还有,请注意笑脸符号!我无法告诉你在这里我干燥的幽默已经引发了多少争论 :-) - paxdiablo

0

strncpy()的语义即使在C++参考文献中被准确地解释,也经常被误解。这个函数的行为是反直觉的,容易出错。

为了避免在使用它或在开发过程中出现问题,当维护者误读代码并添加更多微妙的错误时,有一个简单的解决方案:永远不要使用这个函数

您可以在Bruce Dawson的这篇文章中阅读更多详细信息。

回答您的问题:如果源字符串比作为第三个参数传递的大小(通常对应于目标缓冲区的大小)长,则该函数将复制大小个字符到目标位置,并且这些字符中没有空字节。调用strlen(destination);将会引起未定义的行为,因为它将尝试读取数组末尾之外的内容,直到找到空终止符。这种特定的行为使得strncpy非常容易出错。


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