在C ++ 11中string :: c_str()不再为null终止吗?


72

在C ++ 11中,basic_string::c_str它定义为与完全相同basic_string::data,而后者又定义为*(begin() + n)与和*(&*begin() + n)(when 0 <= n < size())完全相同。

我找不到任何要求字符串末尾始终包含空字符的内容。

这是否意味着c_str()不再保证会产生以null结尾的字符串?


24
当然,如此大的改变会破坏很多旧代码……
Nim

3
@Nim:我完全同意,但是我想知道标准中在哪里规定了这一要求。
Mankarse 2011年

6
如果c_str未返回以NULL结尾的字符串,则它将是有史以来命名最错误的函数。
塞斯·卡内基

1
您错过了=in 0 <= n <= size()…包括标准包一样,一切都很好
Ben Voigt

Answers:


80

现在需要字符串才能在内部使用以空值结尾的缓冲区。看一下operator[](21.4.5)的定义:

要求: pos <= size()

返回: *(begin() + pos)如果pos < size(),否则返回T带有value 类型的对象的引用charT();参考值不得修改。

回顾c_str(21.4.7.1/1),我们看到它的定义如下operator[]

返回:p使得in中p + i == &operator[](i)的每个指针都返回的指针。i[0,size()]

并且c_strdata都必须为O(1),因此有效地强制实现使用以空值结尾的缓冲区。

此外,正如DavidRodríguez-dribeas在评论中指出的那样,返回值要求还意味着您可以&operator[](0)用作的同义词c_str(),因此终止的空字符必须位于相同的缓冲区中(因为*(p + size())必须等于charT());这也意味着即使终止符被延迟初始化,也无法观察中间状态的缓冲区。


6
这并没有说明字符串以null终止。
jalf

21
虽然这并不表示字符串必须以null结尾,但是可以从字符串要求中推断出它。双方c_strdata必须是O(1)操作,这意味着它们不能动态创建一个副本。另外,匹配operator[]输出的要求意味着它已经被nul终止,或者对data/的调用c_str必须在返回指针之前添加nul终止符。此外,在调用之前,字符串必须具有用于该终止符的空间,以保持O(1)要求。从技术上讲,串不一定是NUL终止,但data()确实
大卫·罗德里格斯- dribeas

7
同样,最后一个引号:返回:指针p,对于[0,size()]中的每个i,使p + i ==&operator [](i)。意味着-即,&operator[](size()) == &operator[](size()-1) + 1如果operator[](size())返回\0对字符串外部的引用,则永远无法满足该要求。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2011年

9
@jalf:关于字符串以null终止,这没有说什么。是的,它确实。21.4.7.1说,由返回的指针c_str()必须指向length的缓冲区size()+1。21.4.5说此缓冲区的最后一个元素必须具有值charT()-换句话说,是空字符。
David Hammen

4
@jalf:“这个答案只给了我们推理链的一半。 ”它给了整个推理链的三分之二。缺少的一件事是,默认初始化分配的值charT()是空字符。这显然是的情况下charTchar。该标准对的含义有点含糊(不多含糊)wchar_t
David Hammen

23

好吧,事实上,新标准确实规定.data()和.c_str()现在是同义词。但是,它并不表示.c_str()不再是零终止的:)

这仅意味着您现在也可以依赖.data()零终止了。

论文N2668定义std :: basic_string的c_str()和data()成员如下:

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

返回:指向一个长度为size()+ 1的数组的初始元素的指针,该数组的第一个size()元素等于* this控制的字符串的相应元素,而最后一个元素是charT()指定的空字符。

要求:程序不得更改存储在字符数组中的任何值。

请注意,这并不意味着任何有效的std :: string可以被视为一个C字符串,因为的std :: string可以包含嵌入的空值,直接作为一个const char时将提前结束的C字符串*。

附录:

我无权访问C ++ 11的实际发布的最终规范,但似乎在规范的修订历史中确实删除了该措辞:例如http://www.open-std.org/jtc1/ sc22 / wg21 / docs / papers / 2011 / n3242.pdf

第21.4.7节basic_string字符串操作 [string.ops]

§21.4.7.1 basic_string访问器 [string.accessors]

     const charT* c_str() const noexcept;
     const charT* data() const noexcept;
  1. 返回:指针p,使得in中p + i == &operator[](i)的每个指针。i[0,size()]
  2. 复杂度:恒定时间。
  3. 要求:程序不得更改存储在字符数组中的任何值。

@ R.MartinhoFernandes:我的编辑和您的评论必须有交叉的帖子?
sehe 2011年

1
是的,对此感到抱歉。关于您的编辑,我想指出FDIS的措词与此非常不同,并且对空终止的要求不是很明显,但这是忍者:)
R. Martinho Fernandes

挖掘更多的修订。现在,谁给我买了规范的副本;)
sehe 2011年

请转义出现Operator[](i)在帖子中的方括号,因为当前它们被解释为链接,这使文本难以理解。
凯文·卡斯卡特

@Kevin:对此表示
抱歉

10

“历史”是很久以前,当每个人都在单个线程中工作,或者至少线程是拥有自己数据的工人时,他们为C ++设计了一个字符串类,该类使字符串处理比以前更容易,并且它们重载了运算符+连接字符串。

问题是用户将执行以下操作:

s = s1 + s2 + s3 + s4;

并且每个串联都会创建一个临时对象,该临时对象必须实现一个字符串。

因此,有人会产生“惰性评估”的头脑,以便在内部可以将所有字符串存储在某种“绳子”中,直到有人想要将其读取为C字符串为止,此时您可以将内部表示形式更改为连续的缓冲区。

这解决了上面的问题,但引起了其他麻烦,特别是在多线程世界中,人们期望.c_str()操作为只读/不更改任何内容,因此无需锁定任何内容。在类实现中过早地进行内部锁定,以防万一有人对它进行多线程处理(甚至在没有线程标准的情况下)也不是一个好主意。实际上,做任何事情都比每次简单地复制缓冲区要昂贵得多。字符串实现放弃了“写时复制”实现的相同原因。

因此,做.c_str()一个真正不变的操作被认为是最明智的选择,但是,人们是否可以在现在意识到线程的标准中“依赖”它呢?因此,新标准决定明确声明您可以,因此内部表示形式必须包含空终止符。


旧的string还具有奇怪的属性,即第一个非constbegin()会使迭代器无效!
curiousguy 2015年

2

发现得好。这肯定是最近采用的标准中的一个缺陷。我确定没有意图破坏当前使用的所有代码c_str。我建议提出缺陷报告,或者至少提出问题comp.std.c++(如果它涉及缺陷,通常会提交给委员会)。



好吧,FDIS中有些可以说是不稳定的。21.4.2/2.data()一个空字符串实际上不是以空字符结尾的(.data()+1无效,但是应该是一个超出该指针的指针\0
MSalters 2011年
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.