string.c_str()需要进行内存释放吗?

52

我的代码经常将C++字符串转换为C字符串,我想知道原始字符串是否在堆栈上分配。 C字符串会在堆栈上分配吗?例如:

string s = "Hello, World!";
char* s2 = s.c_str();

s2会在栈上分配还是堆上分配?换句话说,我需要删除s2吗?

相反,如果我有这段代码:

string s = new string("Hello, mr. heap...");
char* s2 = s.c_str();

s2现在会在堆上吗,因为它的原始位置在堆上?

澄清一下,当我问s2是否在堆上时,我知道指针在栈上。我想知道指针所指向的对象是在堆上还是在栈上。


这个回答解决了你的问题吗?std::string.c_str()或std::string.data()返回的指针需要被释放吗? - imz -- Ivan Zakharyaschev
7个回答

48
string s = "Hello world";
char* s2 = s.c_str();

s2会被分配到栈上还是堆上?换句话说...我需要删除s2吗?

不需要 delete s2

如果上述代码在函数内部,则s2位于堆栈上;如果代码处于全局或命名空间作用域,则s2将位于某些静态分配的动态初始化数据段中。无论哪种方式,它都是指向字符的指针(在这种情况下,该字符恰好是s的文本内容的null-terminated string_表示中的第一个'H'字符)。该文本本身位于s对象构建该表示时决定的位置。实现可以以任何方式执行此操作,但对于std::string的关键实现选择是是否提供“短字符串优化”,允许非常短的字符串直接嵌入s对象中,并且"Hello world"是否足够短以从该优化中受益:

  • 如果是这样,那么s2将指向s内存中的地址,这将根据上面对s2的解释而被分配到堆栈或静态内存中。
  • 否则,在s内部将会有一个指向动态分配(自由存储区/堆)内存的指针,其中包含由.c_str()返回地址的“Hello world\0”内容,并且s2将是该指针值的副本。

请注意,c_str()const的,因此为了使您的代码编译通过,您需要更改为const char* s2 = ...

您绝不能delete s2s2指向的数据仍然由s对象拥有和管理,将受到任何调用非const方法的ss超出范围的影响而失效。

string s = new string("Hello, mr. heap...");
char* s2 = s.c_str();

s2现在会在堆上吗,因为它的起源是在堆上?

这段代码无法编译,因为s不是指针,而且字符串没有像string(std::string*)这样的构造函数。您可以将其更改为以下任一选项:

string* s = new string("Hello, mr. heap...");

...或者...

string s = *new string("Hello, mr. heap...");

后者会导致内存泄漏并没有实际用途,因此让我们假设前者。那么:
char* s2 = s.c_str();

...需要变成...

const char* s2 = s->c_str();

s2现在会在堆上吗,因为它的原始数据在堆上?

是的。在所有情况下,特别是如果s本身在堆上,则:

  • 即使s内部有一个短字符串优化缓冲区,c_str()返回一个指针,它也必须在堆上,否则
  • 如果s使用指针来存储文本的进一步内存,则该内存也将从堆中分配。

但再次强调,即使确信s2指向堆分配的内存,您的代码也不需要释放该内存-当删除s时,该操作会自动完成:

string* s = new string("Hello, mr. heap...");
const char* s2 = s->c_str();
// <...use s2 for something...>
delete s;   // "destruct" s and deallocate the heap used for it...

当然,通常最好只使用 string s("xyz");,除非你需要超出局部范围的生命周期,否则可以使用 std::unique_ptr<std::string>std::shared_ptr<std::string>

18

c_str() 方法返回 string 对象中的内部缓冲区指针。您不需要使用 free()/delete 释放它。

该指针仅在指向的 string 对象处于作用域内时才有效。此外,如果您调用了 string 对象的非 const 方法,则不能再保证该指针的有效性。

详见 std::string::c_str


1
有趣,所以您永远不会释放 char*,因为这样做会释放字符串中的内部数据? c_str()字面上保持与字符串实际使用的实际数据相同的地址?多次调用.c_str()会以这种方式返回相同的地址吗? (只是澄清一下,以便我知道我是否理解) - Georges Oates Larsen
@Georges,对于这些问题的答案取决于您是使用最新标准还是旧标准。您可能需要澄清您感兴趣的是哪一个。 - R. Martinho Fernandes
@R.MartinhoFernandes 最好使用最新的标准。不过知道这个标准是多久之前发布的倒是挺有趣的。 - Georges Oates Larsen
2
只要它指向的字符串有效,它才是有效的。同时,只要您不改变拥有缓冲区的字符串。 - James McNellis
1
正确的做法是不要释放它,否则会发生糟糕的事情。至于多次调用,我认为在字符串对象上调用非const方法之前,它将是相同的地址,然后就不能再保证了。 - Brian Roach
@JamesMcNellis - 也没错,我应该指出 - Brian Roach

4
首先,即使您似乎认为如此,您的原始字符串并没有完全分配在堆栈上。如果将您的string s声明为本地变量,则只有string对象本身被“分配在堆栈上”。该字符串对象的受控序列分配在其他地方。您不应该知道它分配在哪里,但在大多数情况下,它分配在堆上。也就是说,通常情况下,无论您在哪里声明s,存储在s中的实际字符串"Hello world"都是在堆上分配的。
其次,关于c_str()
在C++的最初规范(C++98)中,c_str通常返回指向某个独立缓冲区的指针。同样,您不应该知道它分配在哪里,但通常情况下,它应该分配在堆上。大多数std::string的实现都确保它们的受控序列始终以零结尾,因此它们的c_str返回一个直接指向受控序列的指针。
在C++的新规范(C++11)中,现在要求c_str返回指向受控序列的直接指针。
换句话说,在通常情况下,即使对于本地std::string对象,c_str的结果也将指向在堆上分配的内存。在这方面,您的第一个示例与第二个示例没有区别。但是,在任何情况下,由c_str()指向的内存都不属于您。您不应该释放它。您甚至不应该知道它分配在哪里。

“该字符串对象的受控序列是在其他地方分配的。”这完全取决于实现方式(例如SSO),就像R. Martinho Fernandes的回答中所述的那样。 - Peter Mortensen

3

std::string::c_str() 返回的是一个 const char*,而不是一个 char *。这表明你不需要释放它。内存由实例管理(例如,在此链接中查看一些细节),因此只在字符串实例有效时才有效。


7
指向对象的const限定并不表示调用者无需释放该对象。 - James McNellis
@JamesMcNellis:对于对象是正确的。但是char *不完全是一个对象,只是一个普通的C类型。在纯C中,const通常用来表示“你不拥有这块内存”(因为free(3)接受非const指针)。 - vanza
@JamesMcNellis:有哪些这样的函数示例?我认为这非常令人困惑,当返回的const指针所指向的对象需要由调用者释放时,我会非常明确地进行评论。 - leftaroundabout

2

s2只要s仍在范围内,就是有效的。它是指向s拥有的内存的指针。例如,请参见此MSDN文档"该字符串具有有限的生命周期,并由类字符串拥有。"

如果您想在函数中使用std::string作为字符串操作的工厂,然后返回C风格的字符串,您必须为返回值分配堆存储空间。使用mallocnew获取空间,然后复制s.c_str()的内容。


1
s2会分配在栈上还是堆上?
可能会分配在任意一个地方。例如,如果std::string类进行了小字符串优化,当其大小低于SSO阈值时,数据将存储在栈上;否则,数据将存储在堆上。(这一切都假设std::string对象本身位于栈上。)
我需要删除s2吗?
不需要,由c_str返回的字符数组对象是由字符串对象拥有的。
s2现在会在堆上吗,因为它的原始数据在堆上?
在这种情况下,即使进行SSO,数据很可能仍然存储在堆上。但是很少有理由动态分配std::string对象。

0

这要看情况。如果我没记错的话,CString会复制输入的字符串,所以不需要任何特殊的堆分配例程。


我认为他指的是C字符串,而不是CString(Windows上C++类的名称?MFC?)。在这个例子中,结果的类型是指向char的指针。 - Peter Mortensen

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