stringstream,string和char *转换混乱

时间:2009-09-03 16:22:11

标签: c++ string memory stringstream

我的问题可归结为,stringstream.str().c_str()返回的字符串在内存中的位置,以及为什么不能将其分配给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()分配给const char*的假设导致我花了一段时间追踪错误。

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

替换cout语句
cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)

正确打印字符串吗?

我正在Visual Studio 2008中编译。

5 个答案:

答案 0 :(得分:187)

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

您可以将该临时字符串对象复制到其他字符串对象并从该字符串中获取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是最好的解决方案。不幸的是,它并不是很有名。

答案 1 :(得分:13)

你正在做的是创造一个临时的。该临时存在于由编译器确定的范围内,因此它足够长以满足其所处的要求。

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

您的语句string str(ss.str());表示临时用于您放在本地堆栈上的string变量str的构造函数中,并且只要您保留我希望:直到块结束,或者你写的功能。因此,当您尝试const char *时,cout内部仍然是良好的记忆。

答案 2 :(得分:5)

在这一行:

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

ss.str()将生成字符串流内容的副本。当您在同一行上调用c_str()时,您将引用合法数据,但在该行之后,该字符串将被销毁,使您的char*指向无主内存。

答案 3 :(得分: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();

会更好更简单。

答案 4 :(得分:5)

ss.str()初始化完成后,cstr2临时文件将被销毁。因此,当您使用cout进行打印时,与std::string临时关联的c字符串很久就会被破坏,因此如果它崩溃并断言您将会很幸运,如果它打印则不幸运垃圾或似乎工作。

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

然而,cstr1所指向的C字符串与在执行cout时仍然存在的字符串相关联 - 因此它正确地打印了结果。

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

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

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

在GCC中,字符串使用引用计数和写时复制。因此,您会发现以下内容适用(至少在我的GCC版本中)

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

这两个字符串共享相同的缓冲区。当您更改其中一个时,将复制缓冲区,每个缓冲区将保留其单独的副本。但是,其他字符串实现会有所不同。