【问题标题】:Will std::string always be null-terminated in C++11?std::string 在 C++11 中是否总是以空结尾?
【发布时间】:2011-08-29 22:47:52
【问题描述】:

Herb Sutter 在 2008 年在他的网站上发表的一篇文章中指出:

出于与并发相关的原因,有一个积极的提议要在 C++0x 中进一步加强这一点,并要求空终止并可能禁止写时复制实现。这是论文:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2534.html。我认为本文中的一项或两项提案很可能会被采纳,但我们将在下一两次会议上看到。

我知道 C++11 现在保证 std::string 内容连续存储,但他们是否在最终草案中采用了上述内容?

现在使用&str[0] 之类的内容是否安全?

【问题讨论】:

  • 保证在 C++03 中也提供连续存储的内容?

标签: c++ string c++11 language-lawyer null-terminated


【解决方案1】:

是的。根据 C++0x FDIS 21.4.7.1/1,std::basic_string::c_str() 必须返回

一个指针p 使得p + i == &operator[](i) 对应[0,size()] 中的每个i

这意味着给定一个字符串ss.c_str()返回的指针必须与字符串中首字符的地址(&s[0])相同。

【讨论】:

  • 请注意,同样的要求也适用于 data,我认为这不适用于 C++98/03。
  • 是的,这说明basic_string<>::c_str()basic_string<>::data() 现在具有完全相同的语义。
  • 这似乎没有回答帖子标题的问题 - 即“std::string 在 C++11 中是否总是以空结尾?”,在这种情况下答案是否定的. operator[str.length()] 将返回 '\0',但这并不意味着 string 实际上包含它在内存中。
  • @AndrewMarshall: operator[] 需要返回对实际存储元素的引用,因此 (21.4.7.1/1) 还应用了要求 operator[str.length()] 处的元素必须是存储。
  • @S.S.Anne 不,在 this 的情况下,终止符是序列的一部分。并不是说它总是序列的一部分,例如.at().
【解决方案2】:

&str[0] 可以安全使用——只要您不认为它指向以空字符结尾的字符串。

从 C++11 开始,要求包括([string.accessors] 部分):

  • str.data()str.c_str() 指向一个以 null 结尾的字符串。
  • &str[i] == str.data() + i,对于0 <= i <= str.size()
    • 请注意,这意味着存储是连续的。

但是,&str[0] + str.size() 不要求指向空终止符。

当调用data()c_str()operator[](str.size()) 时,符合要求的实现必须将空终止符连续放置在存储中;但不需要将其置于任何其他情况下,例如使用其他参数调用operator[]


为了节省您阅读下面冗长的聊天讨论的时间: 有人提出反对意见,即如果 c_str() 要写入空终止符,则会导致 res.on.data.races#3 下的数据竞争;我不同意这将是一场数据竞赛。

【讨论】:

  • constexpr const CharT* data() const noexcept; 重载不能修改任何东西,所以它必须从一开始就存在
  • @Caleth 您引用的文本是在 C++20 中添加的
  • @M.M 它是一个 const 成员函数,并且至少从 C++11 开始就有 O(1) 要求,如果不是更长的话。事实上,它必须在内部为零终止。编辑:yes it was const prior
  • @Mgetz 放置一个空终止符是 O(1),因为长度是已知的。 const 成员函数允许修改对象的可变内部存储;以及对象持有内部指针的任何动态分配的存储
  • 如果它被允许修改缓冲区,它必须以一种不可能发生数据竞争的方式来做,我不认为这是可能没有每个字符串互斥锁或类似的
【解决方案3】:

尽管 c_str() 返回 std::string 的空终止版本,但在将 C++ std::string 与 C char* 字符串混合时可能会出现意外。

空字符可能会出现在 C++ std::string 中,这可能会导致细微的错误,因为 C 函数会看到更短的字符串。

错误代码可能会覆盖空终止符。这会导致未定义的行为。然后,C 函数将读取字符串缓冲区之外的内容,从而可能导致崩溃。

#include <string>
#include <iostream>
#include <cstdio>
#include <cstring>

int main()
{
    std::string embedded_null = "hello\n";
    embedded_null += '\0';
    embedded_null += "world\n";

    // C string functions finish early at embedded \0
    std::cout << "C++ size: " << embedded_null.size() 
              << " value: " << embedded_null;
    printf("C strlen: %d value: %s\n", 
           strlen(embedded_null.c_str()), 
           embedded_null.c_str());

    std::string missing_terminator(3, 'n');
    missing_terminator[3] = 'a'; // BUG: Undefined behaviour

    // C string functions read beyond buffer and may crash
    std::cout << "C++ size: " << missing_terminator.size() 
              << " value: " << missing_terminator << '\n';
    printf("C strlen: %d value: %s\n", 
           strlen(missing_terminator.c_str()), 
           missing_terminator.c_str());
}

输出:

$ c++ example.cpp
$ ./a.out
C++ size: 13 value: hello
world
C strlen: 6 value: hello

C++ size: 3 value: nnn
C strlen: 6 value: nnna�

【讨论】:

  • "missing_terminator[3] = 'a';" 这显然是 UB。您可以从 NUL 终止符读取,但 you cannot write to it。好吧,除了 NUL 之外,你不能向它写入任何值。
  • 我不会说“c_str() 一般返回”,因为 C++11 it返回一个指向空的指针- 终止字符数组,其数据等同于存储在字符串中的数据。".
  • 用另一个字符替换空终止符是UB。但是嵌入的 null 是否允许?两者都会导致问题,GCC 或 Clang 都不会捕获。
  • 是的,他们是allowed
猜你喜欢
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 1970-01-01
  • 2011-06-29
  • 1970-01-01
  • 2010-10-01
  • 2017-02-15
  • 2011-06-06
相关资源
最近更新 更多