std::string::c_str & 空终止

std::string::c_str & Null termination

我阅读了 std::string::c_str 的各种描述,包括在 years/decades、

上针对 SO 提出的问题

我喜欢这个描述的清晰度:

Returns a pointer to an array that contains a null-terminated sequence of characters (i.e., a C-string) representing the current value of the string object. This array includes the same sequence of characters that make up the value of the string object plus an additional terminating null-character ('[=23=]') at the end.

然而,关于此功能的一些用途仍不清楚。

您可能会认为调用 c_str 可能会在存储在主机对象内部字符数组中的字符串末尾添加一个 [=15=] 字符(std::string):

s[s.size+1] = '[=11=]'

但似乎 std::string 对象在调用 c_str 之前默认为 Null 终止:

查看定义后:

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

我没有看到任何将 [=15=] 添加到 char 数组末尾的代码。据我所知 c_str 只是 returns 一个指向存储在数组第一个元素中的 char 的指针,这与 begin() 非常相似。我什至没有看到检查内部数组是否由 [=15=]

终止的代码

还是我遗漏了什么?

要求是 c_str 必须 return 一个空终止的 cstring。没有说函数必须添加空终止符。大多数实现(我认为所有想要符合标准的实现)都将空终止符存储在字符串本身使用的底层缓冲区中。原因之一是

std::string s;
assert(s[0] == '[=10=]');

必须工作,因为 return string[string.size()] 处的空终止符现在需要字符串。如果字符串没有将空终止符存储在底层缓冲区中,那么 [] 将不得不进行边界检查以查看它是否位于 size() 并且需要 return [=15=].

您看不到将 '[=10=]' 添加到序列末尾的代码,因为空字符已经存在。 c_str 的实现不能 return 指向新数组的指针,因此数组必须存储在 std::string 对象本身上。

因此,您有两种有效的实现方法:

  1. 在构造时始终将 '[=10=]' 存储在 _Myptr() 个字符数组的末尾,或者
  2. 按需复制字符串,在调用c_str()时添加'[=10=]',并在析构函数中删除副本。

第一种方法让您 return _Myptr() 得到 c_str(),但要为每个字符串存储一个额外的字符。第二种方法需要每个 std::string 对象一个额外的指针,因此第一种方法更便宜。

在 C++11 之前,不要求 std::string(或模板化的 class std::basic_string - 其中 std::string 是一个实例)存储一个尾随 '[=12=]'。这反映在 data()c_str() 成员函数的不同规范中 - data() returns 指向基础数据的指针(不需要以 '[= 12=]'c_str() 返回了一个带有终止符 '[=12=]' 的副本。但是,同样地,没有要求不在内部存储尾随 '[=12=]'(访问存储数据末尾之后的字符是未定义的行为)......而且,为了简单起见,一些实现选择无论如何附加尾随 '[=12=]'

在 C++11 中,这发生了变化。本质上,data() 成员函数被指定为提供与 c_str() 相同的效果(即返回的指针指向具有尾随 '[=12=]' 的数组的第一个字符)。这导致 data() 返回的数组需要尾随 '[=12=]',因此在内部表示中。

所以您看到的行为与 C++11 一致 - class 的不变量之一是尾随 '[=12=]'(即构造函数确保情况如此,成员函数修改字符串确保它保持为真,并且所有 public 成员函数都可以依赖它为真)。

您看到的行为与 C++11 之前的 C++ 标准并不矛盾。严格来说,C++11 之前的 std::string 不需要维护尾随 '[=12=]',但同样,实现者可以选择这样做。