Stringstream、 string 和 char * 转换混淆

我的问题可以归结为,从 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 (???)

正确打印字符串?

我正在 VisualStudio2008中编译。

183262 次浏览

你所做的只是创造一个临时的。这个临时值存在于编译器确定的范围内,这样它的长度就足以满足它要到达的目的地的需求。

一旦 const char* cstr2 = ss.str().c_str();语句完成,编译器就会发现没有理由保留这个临时字符串,它就会被销毁,因此您的 const char *指向空闲内存。

您的语句 string str(ss.str());意味着在构造函数中使用临时变量 string变量 str,这个变量放在本地堆栈上,并且一直保持在您预期的位置: 直到块的末尾,或者您已经编写的函数。因此,当您尝试使用 cout时,内部的 const char *仍然是很好的内存。

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();
}

我认为这是最好的解决办法。不幸的是,这并不是众所周知的。

在这一行:

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

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

由 ss.str ()返回的 std: : string 对象是一个临时对象,其生存期仅限于表达式。因此,不能将指针分配给临时对象而不获得垃圾。

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

#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();

会更好更简单。

cstr2的初始化完成后,ss.str()临时被销毁。所以当你用 cout打印它的时候,与 std::string临时文件相关联的 c-string 早就被销毁了,因此如果它崩溃并断言,那么你将是幸运的,而如果它打印垃圾或者看起来工作,那么你就不是幸运的了。

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

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

在下面的代码中,第一个 cstr是正确的(我假设它是实际代码中的 cstr1?).第二个输出与临时字符串对象 ss.str()关联的 c-string。在计算出现该对象的完整表达式结束时,该对象将被销毁。完整表达式是整个 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());

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