调用 string::c_str() 时实际做了什么?

What actually is done when `string::c_str()` is invoked?

调用 string::c_str() 时实际做了什么?

  1. string::c_str()会分配内存,复制字符串对象的内部数据,并在新分配的内存中追加一个空终止符?

  1. 因为 string::c_str() 必须是 O(1),所以不再允许分配内存和复制 string。实际上,始终使用空终止符是唯一合理的实现方式。

有人在 this answer of this question 的评论中说 C++11 要求 std::string 为尾随 '[=17=]'[分配额外的 char。所以看来第二种选择是可能的。

another person saysstd::string 操作 - 例如迭代、串联和元素变异——不需要零终止符。除非您将 string 传递给需要零终止字符串的函数,否则可以省略 .

还有更多voice from an expert:

Why is it common for implementers to make .data() and .c_str() do the same thing?

Because it is more efficient to do so. The only way to make .data() return something that is not null terminated, would be to have .c_str() or .data() copy their internal buffer, or to just use 2 buffers. Having a single null terminated buffer always means that you can always use just one internal buffer when implementing std::string.

所以我现在真的很困惑,调用 string::c_str() 时实际上做了什么?

更新:

如果 c_str() 实现为简单地返回已经分配和管理的指针。

A。由于 c_str() 必须以 null 结尾,因此内部缓冲区需要始终以 null 结尾,即使对于空的 std::string,例如:std::string demo_str;,也应该有一个 [= 24=]demo_str的内存中。我说得对吗?

B。调用std::string::substr()会发生什么?自动将 [=24=] 附加到子字符串?

自 C++11 起,std::string::c_str()std::string::data() 都需要 return 指向字符串内部缓冲区的指针。并且由于 c_str()(但不是 data())必须以 null 终止,这实际上要求内部缓冲区始终以 null 终止,尽管 size()/[ 不计算 null 终止符=15=],或 return 由 std::string 迭代器编辑,等等

在 C++11 之前,c_str() 的行为在技术上是特定于实现的,但我见过的大多数实现都是这样工作的,因为这是实现它的最简单和最明智的方法。 C++11 刚刚标准化了已经广泛使用的行为。

更新

自 C++11 起,缓冲区始终以 null 结尾,即使对于空字符串也是如此。但是,这并不意味着当字符串为空时需要动态分配缓冲区。它可以指向一个 SSO 缓冲区,甚至可以指向一个 static nul 字符。无法保证由 c_str()/data() 编辑的指针 return 仍然指向与字符串内容更改时相同的内存地址。

std::string::substr() returns 一个新的 std::string 有自己的空终止缓冲区。从中复制的字符串不受影响。

这是 .c_str() 的复杂度为 o(1) 的经验“证明”:

#include <stdio.h>
#include <string>
using namespace std;
int main(int argc, char **argv)
{
    std::string x(5000000, 'b'); // <--- single time allocation
    // std::string x(5, 'b'); // <--- compare to a much shorter string
    for (unsigned int i=0;i<1000000;i++)
    {
        const char *y = x.c_str(); // <--- copy entire content ?
    }
}
  • 使用 -O0 编译以避免优化任何东西
  • 计时 2 版本:我得到 相同 性能
  • 这是一个经验“证明”(至少我机器的实现)
    • 提取空终止字符串的内部表示
    • 不会在每次调用 .c_str() 时都复制内容。

已经提供了很多很好的答案和评论。但是为了证明 std::string 通常由空终止字符串支持,我提供了一个简单但天真的实现。它不完整,不进行错误检查,当然也没有优化。但它足够完整,可以向您展示字符串 class 通常是如何使用空终止缓冲区作为成员变量来实现的。

class string
{
public:

    string()
    {
        assign("", 0);
    }

    string(const char* s)
    {
        assign(s, strlen(s));
    }

    string(const char* s, size_t len)
    {
        assign(s, len);
    }

    string(const string& s)
    {
        assign(s._ptr, s._len);
    }

    ~string()
    {
       delete [] _ptr;
    }

    string& operator=(const string& s)
    {
        const char* oldptr = _ptr;
        assign(s._ptr, s._len);
        delete [] oldptr;
    }

    const char* data()
    {
        return _ptr;
    }

    const char* c_str()
    {
       return _ptr;
    }

    size_t length()
    {
        return _len;
    }

    // substr always returns a new string
    std::string substr(size_t pos, size_t count)
    {
        std::string s(_ptr+pos, count);
        return s;  
    }

private:
    char* _ptr;
    size_t _len;

    void assign(const char* ptr, size_t len)
    {
        _len = len;        
        _ptr = new char[_len+1]; // +1 for null termination
        memcpy(_ptr, ptr, len); 
        _ptr[_len] = '[=10=]';       // always null terminate
    }
};