150

我的问题可以归结为,从stringstream.str().c_str()内存中返回的字符串在哪里,为什么不能分配给 a const char*

此代码示例将比我更好地解释它

#include <string>
#include <sstream>
#include <iostream>

using namespace std;

int main()
{
    stringstream ss("this is a string\n");

    string str(ss.str());

    const char* cstr1 = str.c_str();

    const char* cstr2 = ss.str().c_str();

    cout << cstr1   // Prints correctly
        << cstr2;   // ERROR, prints out garbage

    system("PAUSE");

    return 0;
}

stringstream.str().c_str()可以分配给 a的假设const char*导致了我花了一段时间才找到的错误。

对于奖励积分,任何人都可以解释为什么cout

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

正确打印字符串?

我在 Visual Studio 2008 中编译。

4

5 回答 5

212

stringstream.str()返回一个在完整表达式末尾被销毁的临时字符串对象。如果你从那个 ( ) 中得到一个指向 C 字符串的指针stringstream.str().c_str(),它将指向一个在语句结束处被删除的字符串。这就是您的代码打印垃圾的原因。

您可以将该临时字符串对象复制到其他字符串对象,并从该对象中获取 C 字符串:

const std::string tmp = stringstream.str();
const char* cstr = tmp.c_str();

请注意,我制作了临时字符串const,因为对它的任何更改都可能导致它重新分配并因此变得cstr无效。因此,完全不存储调用结果str()cstr仅在完整表达式结束之前使用会更安全:

use_c_str( stringstream.str().c_str() );

当然,后者可能并不容易,而且复制成本可能太高。您可以做的是将临时绑定到const引用。这会将其生命周期延长到引用的生命周期:

{
  const std::string& tmp = stringstream.str();   
  const char* cstr = tmp.c_str();
}

IMO 这是最好的解决方案。不幸的是,它不是很为人所知。

于 2009-09-03T16:25:21.457 回答
14

你正在做的是创建一个临时的。该临时存在于编译器确定的范围内,因此它的长度足以满足其去向的要求。

一旦语句const char* cstr2 = ss.str().c_str();完成,编译器就认为没有理由保留临时字符串,并且它被销毁,因此您const char *指向释放的内存。

您的声明string str(ss.str());意味着临时变量在构造函数中用于您放置在本地堆栈上的string变量str,并且只要您期望,它就会一直存在:直到块结束或您编写的函数。因此,const char *当您尝试cout.

于 2009-09-03T16:25:18.693 回答
6

在这一行:

const char* cstr2 = ss.str().c_str();

ss.str()复制字符串流的内容。当您c_str()在同一行调用时,您将引用合法数据,但在该行之后,字符串将被销毁,让您char*指向未拥有的内存。

于 2009-09-03T16:27:46.100 回答
5

ss.str() 返回的 std::string 对象是一个临时对象,其生命周期仅限于表达式。所以你不能分配一个指向临时对象的指针而不得到垃圾。

现在,有一个例外:如果您使用 const 引用来获取临时对象,那么在更长的生命周期内使用它是合法的。例如你应该这样做:

#include <string>
#include <sstream>
#include <iostream>

using namespace std;

int main()
{
    stringstream ss("this is a string\n");

    string str(ss.str());

    const char* cstr1 = str.c_str();

    const std::string& resultstr = ss.str();
    const char* cstr2 = resultstr.c_str();

    cout << cstr1       // Prints correctly
        << cstr2;       // No more error : cstr2 points to resultstr memory that is still alive as we used the const reference to keep it for a time.

    system("PAUSE");

    return 0;
}

这样你就可以获得更长的字符串时间。

现在,您必须知道有一种称为 RVO 的优化,它说如果编译器通过函数调用看到初始化并且该函数返回一个临时值,它不会进行复制,而只是使分配的值成为临时值. 这样你就不需要实际使用引用,只有当你想确保它不会复制它是必要的。这样做:

 std::string resultstr = ss.str();
 const char* cstr2 = resultstr.c_str();

会更好更简单。

于 2009-09-03T16:30:28.107 回答
5

初始化完成后,ss.str()临时对象被销毁cstr2。因此,当您使用 打印它时cout,与该std::string临时文件相关联的 c 字符串早已被破坏,因此如果它崩溃并断言,您将很幸运,如果它打印垃圾或看起来确实有效,则不幸运。

const char* cstr2 = ss.str().c_str();

cstr1但是,指向的 C 字符串与在您执行操作时仍然存在的字符串相关联cout- 因此它可以正确打印结果。

在下面的代码中,第一个cstr是正确的(我假设它cstr1在真实代码中?)。第二个打印与临时字符串对象关联的 c 字符串ss.str()。该对象在评估它出现的完整表达式结束时被销毁。full-expression 是整个cout << ...表达式 - 因此在输出 c 字符串时,关联的字符串对象仍然存在。因为cstr2——它成功是纯粹的坏事。它很可能在内部为新临时选择相同的存储位置,它已经为用于初始化的临时选择cstr2。它也可能崩溃。

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

c_str()will 通常只指向内部字符串缓冲区的返回- 但这不是必需的。例如,如果其内部实现不连续,则该字符串可以构成一个缓冲区(这很有可能——但在下一个 C++ 标准中,字符串需要连续存储)。

在 GCC 中,字符串使用引用计数和写时复制。因此,您会发现以下情况成立(至少在我的 GCC 版本上确实如此)

string a = "hello";
string b(a);
assert(a.c_str() == b.c_str());

这两个字符串在这里共享同一个缓冲区。当您更改其中一个时,缓冲区将被复制,并且每个缓冲区都将保存其单独的副本。不过,其他字符串实现的做法有所不同。

于 2009-09-03T16:33:34.373 回答