将 strncpy 转换为没有空终止符空间的字符串是不是安全?
Posted
技术标签:
【中文标题】将 strncpy 转换为没有空终止符空间的字符串是不是安全?【英文标题】:Is it Safe to strncpy Into a string That Doesn't Have Room for the Null Terminator?将 strncpy 转换为没有空终止符空间的字符串是否安全? 【发布时间】:2019-01-10 16:03:25 【问题描述】:考虑以下代码:
const char foo[] = "lorem ipsum"; // foo is an array of 12 characters
const auto length = strlen(foo); // length is 11
string bar(length, '\0'); // bar was constructed with string(11, '\0')
strncpy(data(bar), foo, length);
cout << data(bar) << endl;
我的理解是string
s 总是分配有一个隐藏的空元素。如果是这种情况,那么bar
确实分配了 12 个字符,其中第 12th 是隐藏的'\0'
,这是非常安全的......如果我错了,那么@987654326 @ 将导致未定义的行为,因为没有空终止符。
谁能帮我确认一下?这合法吗?
关于为什么使用strncpy
而不是仅仅使用string(const char*, const size_t)
构造函数,存在很多问题。我的意图是让我的玩具代码接近包含vsnprintf
的实际代码。不幸的是,即使在这里得到了很好的答案,我发现vsnprintf
的行为与strncpy
不同,我在这里提出了一个后续问题:Why is vsnprintf Not Writing the Same Number of Characters as strncpy Would?
【问题讨论】:
怎么可能不呢?当然,假设您复制的字节数不超过可用缓冲区空间。 @TrebuchetMS 是的,谢谢,我已经修正了该评论。 你有这方面的实际用例吗?如果你给std::string
一个 c 字符串,它会做同样的事情而不会让人头疼。
@NathanOliver 是的,我正在使用vsnprintf
来填充string
。似乎只是增加了要问的问题的复杂性,并没有强迫这个问题。
那为什么不用std::string bar( foo, length );
而不是讨厌的strncpy()
?
【参考方案1】:
这是安全的,只要将[0, size())
字符复制到字符串中即可。每[basic.string]/3
在所有情况下,
[data(), data() + size()]
都是有效范围,data() + size()
指向值为charT()
(“空终止符”)的对象,size() <= capacity()
是true
。
所以string bar(length, '\0')
为您提供了一个size()
为11 的字符串,末尾带有一个不可变的空终止符(实际大小总共有12 个字符)。只要您不覆盖该空终止符,或尝试将其写过去,就可以。
【讨论】:
不确定他是否会好起来 -std::string::length()
会给出“错误”的信息
@Slava 怎么会出错?字符串以11
的大小开始,除非他们使用字符串函数,否则无法更改。如果他们只复制 5 个字符,他们仍然有一个大小为 11 的字符串,它只是有 6 个额外的空值终止它。
我把“错误”放在双引号中。是的,它会显示正确大小的缓冲区,但将其用作字符串可能会导致丑陋的问题,在使用此字符串时很难捕捉和修复。
@Slava a std::string
允许包含空字符,这与 C 字符串不同。
@Slava “因为大多数开发人员都希望 length() 为您提供字符串的长度,而不是缓冲区的大小。” 但这正是它的作用。字符串是字节序列。这个结尾有一些空字节。您应该改掉将“string”视为等同于“c-string”的习惯。 (同时,由于.reserve()
等等,缓冲区可能会更大一些)【参考方案2】:
这里有两种不同的东西。
首先,strncpy
在这种情况下是否添加了额外的\0
(11 个非\0
元素将被复制到大小为 11 的字符串中)。答案是否定的:
将src指向的字节串的最多count个字符(包括终止的空字符)复制到dest指向的字符数组中。
如果在复制整个字符串 src 之前达到 count,则生成的字符数组不是以 null 结尾的。
所以通话完全没问题。
然后data()
给你一个正确的\0
-终止字符串:
c_str() 和 data() 执行相同的功能。 (C++11 起)
看来,对于 C++11,你是安全的。该字符串是否分配了额外的\0
似乎没有在文档中说明,但API很清楚你所做的一切都很好。
【讨论】:
为了扩展你的陈述,你是说string
没有分配了一个隐藏的空终止符?
分配必须以这种方式工作才能使c_str()
/data()
成为O(1)
,因为索引str[str.size()]
为您提供'\0'
(C++11 起)。在实践中,他们总是以这种方式工作。【参考方案3】:
您分配了一个 11 个字符的 std::string
。您不会尝试读取或写入超出此范围的任何内容,因此该部分将是安全的。
所以真正的问题是你是否弄乱了字符串的内部结构。既然你没有做过任何不允许的事情,那怎么可能呢?如果字符串需要在内部保留一个 12 字节的缓冲区并在末尾填充一个空值以履行其约定,那么无论您执行什么操作都是如此。
【讨论】:
【参考方案4】:是的,根据char * strncpy(char* destination, const char* source, size_t num) 是安全的:
从字符串中复制字符
将源的前 num 个字符复制到目标。如果在复制 num 个字符之前找到源 C 字符串的结尾(由空字符表示),则用零填充目标,直到总共写入了 num 个字符。
【讨论】:
问题是有一个额外的字符需要复制。 不是问题,显然strncpy
不会写出越界。问题是cout
read 是否会越界。请更正答案以解决问题或删除。
@Matthieu - 不一定。如果这是std::string
,那就是目标,它会自我意识到它的长度。
@StoryTeller 虽然缺少\0
(与data(bar)
一起使用时)。使用字符串本身确实很好。
@Matthieu - 只有当操作需要目标缓冲区来保存以空字符结尾的字符串时,这才是问题。以上是关于将 strncpy 转换为没有空终止符空间的字符串是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章