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());
意味着在构造函数中使用了临时的string
variablesstr
,这个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();
会更好,更简单。