在C++11中,string::c_str()方法是否不再以null结尾?

72
在C++11中,basic_string::c_str被定义为与basic_string::data完全相同,而basic_string::data则被定义为恰好等同于*(begin() + n)*(&*begin() + n)(当 0 <= n < size() )。
我没有找到任何要求该字符串必须始终以空字符结尾的内容。
这是否意味着不能保证c_str()将产生以空字符结尾的字符串?

24
这样激进的变化肯定会破坏很多旧代码... - Nim
3
@Nim:我完全同意,但我想知道这个要求在标准的哪里规定了。 - Mankarse
6
如果c_str函数不能返回以NULL结尾的字符串,那它将成为最容易误解的函数名称。 - Seth Carnegie
2
你在 0 <= n <= size() 中漏掉了一个 = ... 当你加上它时,一切都会正常,就像标准所做的那样。 - Ben Voigt
4个回答

81
现在要求使用以null结尾的缓冲区来内部处理字符串。查看operator[](21.4.5)的定义:

Requires: pos <= size().

Returns: 如果pos < size(),则返回*(begin() + pos),否则返回类型为T的对象的引用,该对象具有值charT();不得修改所引用的值。

回顾c_str(21.4.7.1/1),我们看到它是以operator[]为基础定义的:

Returns: 指针p,使得对于[0,size()]中的每个i,都有p + i == &operator[](i)

而且c_strdata都需要O(1),因此实现效果被迫使用以null结尾的缓冲区。
此外,正如David Rodríguez - dribeas在评论中指出的那样,返回值要求还意味着您可以将&operator[](0)用作c_str()的同义词,因此终止null字符必须位于同一缓冲区中(因为*(p + size())必须等于charT());这也意味着即使惰性初始化终止符,也无法观察缓冲区在中间状态下的情况。

6
这并没有说明这个字符串是以空字符结尾的。 - jalf
23
虽然这并不意味着字符串必须以空字符结尾,但可以从字符串的要求中推断出来。c_strdata操作必须是O(1)的,这意味着它们不能在运行时创建副本。此外,匹配operator[]输出的要求意味着它已经以null结尾,或者在返回指针之前调用data/c_str时必须添加null终止符号。另外,在调用时,字符串必须有足够的空间来存储终止符以满足O(1)的要求。从技术上讲,字符串并不需要以null结尾,但data()操作会自动添加。 - David Rodríguez - dribeas
7
最后一句引用的意思是:返回一个指针p,使得对于每个i在[0,size()]范围内,p + i == &operator。这意味着&operator[](size()) == &operator[](size()-1) + 1 --也就是说,如果operator[](size())返回了指向字符串外部的\0的引用,那么这个要求将永远无法满足。 - David Rodríguez - dribeas
9
@jalf: 这并没有说明字符串必须以空字符结尾。 是的,它有。21.4.7.1指出c_str()返回的指针必须指向一个长度为size()+1的缓冲区。而21.4.5则表明该缓冲区的最后一个元素必须是charT(),也就是空字符。 - David Hammen
4
“这个答案只给了我们推理链的一半。”它提供了完整链的三分之二。唯一缺少的是默认初始化charT()赋予的值是空字符。当 charTchar时,显然如此。标准对wchar_t的含义有些模糊(比有些模糊还要更模糊)。 - David Hammen
显示剩余17条评论

23

实际上,新标准规定了.data()和.c_str()现在是同义词。然而,它并未说明.c_str()不再以零结尾 :)

这意味着您现在也可以依赖于.data()以零结尾。

Paper N2668 defines c_str() and data() members of std::basic_string as follows:

 const charT* c_str() const; 
 const charT* data() const; 

Returns: A pointer to the initial element of an array of length size() + 1 whose first size() elements equal the corresponding elements of the string controlled by *this and whose last element is a null character specified by charT().

Requires: The program shall not alter any of the values stored in the character array.

请注意,这并不意味着任何有效的std::string都可以被视为C字符串,因为std::string可能包含嵌入的null,直接将其用作const char*时会过早地结束C字符串。
附录: 我无法访问实际发布的C++11 最终规范, 但似乎在规范的修订历史中确实删除了某些措辞:例如http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf

§ 21.4.7基本字符串操作 [string.ops]

§ 21.4.7.1基本字符串访问器 [string.accessors]

     const charT* c_str() const noexcept;
     const charT* data() const noexcept;
  1. Returns: A pointer p such that p + i == &operator[](i) for each i in [0,size()].
  2. Complexity: constant time.
  3. Requires: The program shall not alter any of the values stored in the character array.

@R.MartinhoFernandes:我的编辑和你的评论可能是同时发生的? - sehe
1
是的,很抱歉。关于你的编辑,我想指出FDIS措辞与此非常不同,对于空终止的要求并不明显,但它被巧妙地隐藏了:) - R. Martinho Fernandes
挖掘了一些更多的修订版本。现在,谁能给我买那份规范书呢 ;) - sehe
请在您的帖子中转义出现的Operator[](i)中的方括号,因为它们当前被解释为链接,这使得文本难以理解。 - Kevin Cathcart
@Kevin:抱歉,已修复。 - sehe

10

"历史"是这样的,很久以前当每个人都在单线程工作时,或者至少线程是带有自己数据的工作者,他们为C++设计了一个字符串类,使字符串处理比以前更容易,并重载了operator+来连接字符串。

问题在于用户会做类似以下的操作:

s = s1 + s2 + s3 + s4;

每次串联字符串都会创建一个临时变量,必须实现一个字符串,因此有人想到了“惰性评估”:在内部可以存储某种带有所有字符串的“绳索”,直到有人想要将其作为C字符串读取,此时您将更改内部表示以形成连续缓冲区。

这解决了上述问题,但引起了许多其他问题,特别是在多线程世界中,在那里人们期望.c_str()操作是只读的/不会改变任何内容,因此不需要锁定任何内容。在类实现中过早进行内部锁定以防止某些人进行多线程处理(当时甚至没有线程标准)也不是一个好主意。实际上,与其做任何这样的事情,每次复制缓冲区都更加昂贵。出于同样的原因,“写时复制”实现被放弃用于字符串实现。

因此,使.c_str()成为一个真正的不可变操作是最明智的选择,但在现在已经具备线程感知能力的标准中,是否可以“依赖”它?因此,新标准决定明确说明您可以这样做,因此内部表示需要保存空终止符。


旧的 string 还有一个奇怪的特性,即第一个非 const 的 begin() 会使迭代器失效! - curiousguy

2

非常敏锐的发现。这显然是最近采用的标准中的一个缺陷;我相信并没有意图破坏当前使用c_str的所有代码。我建议您提交缺陷报告,或者至少在comp.std.c++中提出问题(如果涉及缺陷,则通常会提交给委员会)。


请返回翻译文本:无需链接 - sehe
嗯,FDIS中有一些可能不太稳定的位。21.4.2/2表示空字符串的.data()实际上并没有以null结尾(.data()+1不是有效的指针,但应该是\0之后的一个指针)。 - MSalters

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