std::string::c_str和空终止符

9
我已阅读了关于 std::string::c_str 的各种描述,包括多年/几十年来在SO上提出的问题。
我喜欢这个说明的清晰度:
返回一个指针,该指针指向一个数组,该数组包含表示字符串对象的当前值的以 null 结尾的字符序列(即 C 字符串)。该数组包括与组成字符串对象值相同的字符序列以及额外的一个终止空字符 ('\0')。
然而,有关此函数目的的某些事情仍不清楚。
你可能会认为调用c_str会向存储在主机对象 (std::string) 的内部 char 数组中添加一个\0字符。
s[s.size+1] = '\0'

但是似乎默认情况下,std::string对象在调用c_str之前就已经以空字符结尾:

enter image description here

查看定义后:

const _Elem *c_str() const _NOEXCEPT
{   // return pointer to null-terminated nonmutable array
    return (this->_Myptr());
}

我没有看到任何代码会在字符数组末尾添加\0。据我所知,c_str只是返回指向存储在数组第一个元素中的字符的指针,就像begin()一样。我甚至没有看到检查内部数组是否以\0结尾的代码。
或者我漏掉了什么?

4
你错过了 C++ 2003 标准和 C++ 2011 标准之间的区别。在 C++ 2011 标准之前,std::string 类型的对象可能会存储没有结束符的字符串。 - Vlad from Moscow
1
  1. 字符串创建或修改时会自动添加零。
  2. 字符串常量“123456789”始终为ASCIIZ,并在末尾具有零字符。
- nick_n_a
3
显然,函数 c_str() 每次被调用时并没有在数组末尾追加 0,因为这将需要分配一个新的数组,并且调用者(也就是你)每次都需要释放它。 - barak manos
2
@nick_n_a - 字符串字面量不需要用ASCII或ASCIIZ(不管那是什么)表示。它们需要有一个空终止符。 - Pete Becker
2
@tuk 你会注意到 c_str 被标记为 const,这意味着根据当前标准中的语言,它应该是相对无数据竞争的。如果你在一个 const 方法中修改字符串,你需要加锁,因为你需要验证值是否已经改变。标准要求 c_str 也是 O(1),这实质上意味着内部表示必须是零终止的。 - Mgetz
显示剩余3条评论
3个回答

12
在C++11之前,std::string(或模板类std::basic_string的实例-其中std::string是一个实例)不需要存储尾随的'\0'。这反映在data()c_str()成员函数的不同规范中 - data()返回指向底层数据的指针(不需要以'\0'结尾),而c_str()返回一个带有终止'\0'的副本。然而,同样地,没有要求在内部不存储尾随的'\0'(访问存储的数据之外的字符会导致未定义的行为)...为了简单起见,一些实现选择在后面附加一个尾随的'\0'
随着C++11的到来,这种情况发生了变化。基本上,将data()成员函数指定为与c_str()具有相同的效果(即返回的指针是指向具有尾随'\0'的数组的第一个字符)。这意味着data()返回的数组需要具有尾随的'\0',因此内部表示也需要如此。
因此,您看到的行为与C++11一致-类的一个不变量是尾随的'\0'(即构造函数确保这是正确的,修改字符串的成员函数确保它仍然是正确的,并且所有公共成员函数都可以依赖它是正确的)。
您所看到的行为与C++11之前的C++标准并不矛盾。严格来说,std::string在C++11之前不需要保持尾随的'\0',但同样地,实现者可以选择这样做。

6
您不会看到添加'\0'到序列末尾的代码,因为null字符已经存在。 c_str的实现不能返回指向新数组的指针,因此该数组必须存储在std::string对象本身上。
因此,有两种有效的实现方法:
  1. 始终在构建过程中将'\0'存储在字符数组_Myptr()的末尾,或者
  2. 按需复制字符串,在调用c_str()时添加'\0',并在析构函数中删除副本。
第一种方法允许您返回_Myptr()用于c_str(),但每个字符串都需要多存储一个字符。第二种方法需要每个std::string对象的额外指针,因此第一种方法更为节省成本。

1
要求是 c_str 必须返回以null结尾的C字符串。 没有任何规定该函数必须添加null终止符。 大多数实现(我认为所有想要符合标准的实现都是如此)将null终止符存储在字符串本身使用的底层缓冲区中。 其中一个原因是
std::string s;
assert(s[0] == '\0');

由于现在要求字符串返回null终止符,因此必须起作用,即string[string.size()]。如果字符串没有将null终止符存储在基础缓冲区中,则[]需要进行边界检查,以查看是否处于size()位置并需要返回\0

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