为什么C ++ 11使std :: string :: data()添加一个空终止符?

时间:2019-07-02 11:06:15

标签: c++ string c++11 stdstring c++03

以前是std::string::c_str()的工作,但是从C ++ 11开始,data()也提供了它,为什么c_str()的空终止字符被添加到{{ 1}}?在我看来,这似乎是浪费CPU周期,在null终止字符根本不相关并且仅使用std::string::data()的情况下,C ++ 03编译器不必关心终止符,并且不必在每次调整字符串大小时都将0写入终止符,但是由于data()-null-guarantee,C ++ 11编译器必须浪费每次写入字符串0的周期调整了大小,因此,由于它可能会使代码变慢,因此我想他们有一定的理由添加该保证,那是什么?

4 个答案:

答案 0 :(得分:28)

这里有两点要讨论:

空终止符的空间

理论上,C ++ 03实现 可以避免为终止符分配空间和/或可能需要执行复制操作(例如unsharing)。

但是,所有理智的实现都为null终止符分配了空间,以支持c_str()的开始,因为否则,如果这不是一个琐碎的调用,那实际上将是不可用的。

空终止符本身

确实有一些very(1999),very old implementations(2001)每次\0都写了c_str()

但是,主要实现changed(2004)或already like that(2010)避免了在C ++ 11发布之前采用这种方式,因此,当新标准问世时,对于许多用户来说,什么也没有改变了。

现在,是否应使用C ++ 03实现:

  

在我看来,这似乎浪费了CPU周期

不是。如果多次调用c_str(),则已经多次写入,已经在浪费周期。不仅如此,您还使高速缓存层次结构混乱,在多线程系统中考虑这一点很重要。回想一下,多核/ SMT CPU开始出现在20012006之间,这解释了向现代非CoW实现的转换(即使在此之前的几十年中就有多CPU系统) )。

唯一不会保存任何内容的情况是,如果您从不打电话给c_str()。但是,请注意,在重新调整字符串大小时,无论如何都在重写所有内容。额外的字节几乎无法测量。

换句话说,通过 not 在调整大小时编写终止符,您使自己暴露于更差的性能/延迟中。通过同时一次编写一次 ,您必须执行字符串的复制,因此性能行为更容易预测,并且如果最终使用c_str()可以避免性能下降,特别是在多线程系统。

答案 1 :(得分:24)

更改的优点:

  1. data也保证为空终止符时,程序员无需了解c_strdata之间差异的晦涩细节,因此可以避免传递未定义的行为不能保证空终止的字符串变成需要空终止的函数。此类功能在C接口中无处不在,而C接口在C ++中已大量使用。

  2. 下标运算符也已更改为允许对str[str.size()]进行读取访问。不允许访问str.data() + str.size()将会是不一致的。

  3. 虽然在调整大小时不初始化空终止符等可能会使该操作更快,但它会在c_str中强制初始化,这会使该功能变慢¹。删除的优化案例并不是普遍更好的选择。考虑到第2点中提到的更改,缓慢性也会影响下标运算符,这肯定对性能不可接受。因此,无论如何,空终止符都会存在,因此保证它不会有不利的影响。

好奇的细节:str.at(str.size())仍然抛出异常。

P.S。还有另一个更改,就是要确保字符串具有连续的存储空间(这就是为什么首先提供data的原因)。在C ++ 11之前,实现可能使用绳索字符串,并在调用c_str时重新分配。 (据我所知)没有任何重大的实现选择利用这种自由。

P.P.S例如,GCC的libstdc ++的旧版本显然仅在c_str中设置了空终止符,直到3.4版。有关详细信息,请参见related commit


¹对此的一个因素是并发性,它是C ++ 11中的语言标准引入的。并发的非原子性修改是数据争用未定义的行为,这就是为什么允许C ++编译器进行积极的优化并将其保存在寄存器中的原因。因此,使用普通C ++编写的库实现将具有UB,用于同时调用.c_str()

在实践中(请参见注释),有多个线程编写 same 不会引起正确性问题,因为用于实际CPU的asm没有UB。 C ++ UB规则意味着,多个线程实际上修改一个std::string对象(而不是调用c_str())而没有同步,这是编译器+库可以假定不会发生的事情。 / p>

但是它会弄脏缓存并阻止其他线程读取它,因此仍然是一个糟糕的选择,特别是对于可能具有并发读取器的字符串。此外,由于商店的副作用,这也会阻止.c_str()进行优化。

答案 2 :(得分:13)

问题的前提是有问题的。

字符串类必须做很多事情,例如分配动态内存,将字节从一个缓冲区复制到另一个缓冲区,释放基础内存等等。

您不高兴的是一件糟糕的mov汇编指令吗?相信我,这不会对您的表现造成0.5%的影响。

编写程序语言运行时时,您不必沉迷于每条小的汇编指令。您必须明智地选择优化方案,而优化不引人注意的空终止并不是其中之一。

在这种特定情况下,与C兼容比空终止更为重要。

答案 3 :(得分:2)

实际上,这是另一回事。

在C ++ 11之前,c_str()从理论上讲可能具有成本“附加周期”和副本,以确保缓冲区末尾存在空终止符。

这是不幸的,特别是因为它可以非常简单地修复,而实际上没有额外的运行时成本,只需在每个缓冲区的末尾简单地合并一个空字节即可。只需分配一个额外的字节(和一个十几岁的小写),使用时就无需运行时开销,以换取线程安全性和合理性。

完成此操作后,c_str()从定义上实际上与data()相同。因此,对data()的“更改”实际上是免费提供的。没有人向data()的结果中添加额外的字节;它已经在那里。

重要的是,大多数实现都已经在C ++ 03下完成了这一工作,以避免c_str()造成的假设运行时成本。

因此,简而言之,这几乎可以肯定让您付出了任何代价。