stringstream,string和char *转换混淆

我的问题可以归结为,从stringstream.str().c_str()返回的string在内存中,为什么不能被分配给一个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*的假设导致了一个我花了一段时间追踪的bug。

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

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

正确打印string?

我在Visual Studio 2008中编译。

stringstream.str()返回一个临时string对象,在完整expression式的末尾被销毁。 如果从( stringstream.str().c_str() )中得到一个指向Cstring的指针,它将指向一个在语句结束时被删除的string。 这就是为什么你的代码打印垃圾。

您可以将该临时string对象复制到某个其他string对象,并从该string对象中取出Cstring:

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

请注意,我做了临时stringconst ,因为任何更改它可能会导致它重新分配,从而呈现cstr无效。 因此,为了不把调用的结果存储到str()中并且只在整个expression式的结尾使用cstr才更安全:

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

当然,后者可能不容易,复制可能太昂贵。 你可以做的是将临时绑定到一个const引用。 这将延长其使用期限到参考文献的生命周期:

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

IMO是最好的解决scheme。 不幸的是它不是很有名。

你在做什么是创造一个临时的。 该临时存在于由编译器确定的范围内,从而足以满足其正在进行的要求。

只要声明const char* cstr2 = ss.str().c_str(); 是完整的,编译器认为没有理由保持临时string,并将其销毁,因此您的const char *指向free'd内存。

你的语句string str(ss.str()); 意味着在构造函数中使用了临时的stringvariablesstr ,这个string放在本地堆栈上,并且只要你期待:直到块的结尾,或者你写的函数。 因此,当你尝试cout时, const char *内部仍然是很好的记忆。

在这一行中:

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

ss.str()复制 stringstream的内容。 当你在同一行调用c_str()时,你会引用合法的数据,但是在这行之后,string将被销毁,留下你的char*指向无主存储器。

ss.str()初始化完成后, ss.str()临时被销毁。 所以当你用cout打印的时候,和std::string临时关联的c-string已经被删除了,所以如果崩溃和断言,你将会是幸运的,如果打印出垃圾或者看起来不幸运工作。

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

但是, cstr1指向的Cstring与在执行cout时仍然存在的string相关联,因此它正确地输出了结果。

在下面的代码中,第一个cstr是正确的(我假设它是在真正的代码cstr1 ?)。 第二个打印与临时string对象ss.str()相关联的cstring。 在评估出现的完整expression式时,该对象被销毁。 完整expression式是整个cout << ...expression式 – 所以在输出c-string时,关联的string对象仍然存在。 对于cstr2 ,它是成功的纯粹的cstr2 。 它最有可能在内部为临时用于初始化cstr2的临时select相同的存储位置。 它可能会崩溃。

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

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

在GCC中,string使用引用计数和写时复制。 因此,你会发现以下情况是成立的(至less在我的GCC版本上)

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

这两个string在这里共享相同的缓冲区。 当你改变其中的一个时,缓冲区将被复制,并且每个都将保存其单独的副本。 不过,其他string实现方式却有所不同。

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

现在,有一个例外:如果你使用一个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; } 

这样你得到string更长的时间。

现在,你必须知道有一种叫做RVO的优化,它说如果编译器通过一个函数调用看到一个初始化,并且这个函数返回一个临时的,它就不会做这个拷贝,而只是让这个赋值成为临时的。 这样,你不需要真正使用引用,只有当你想确保它不会复制它是必要的。 这样做:

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

会更好,更简单。