C++ 字符串流、字符串和字符*转换混淆
我的问题可以归结为,从C++ 字符串流、字符串和字符*转换混淆,c++,string,memory,stringstream,C++,String,Memory,Stringstream,我的问题可以归结为,从stringstream.str().c_str()返回的字符串在内存中的位置,以及为什么不能将其分配给常量字符* 这个代码示例比我能更好地解释它 #include <string> #include <sstream> #include <iostream> using namespace std; int main() { stringstream ss("this is a string\n"); string
stringstream.str().c_str()
返回的字符串在内存中的位置,以及为什么不能将其分配给常量字符*
这个代码示例比我能更好地解释它
#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;
}
正确打印字符串
我正在Visual Studio 2008中编译。您所做的是创建一个临时文件。该临时变量存在于编译器确定的范围内,因此它的长度足以满足它要去的地方的要求 只要语句
const char*cstr2=ss.str().c_str()代码>完成后,编译器没有理由保留临时字符串,它被销毁,因此您的const char*
指向空闲的内存
您的语句stringstr(ss.str())
意味着临时变量在构造函数中用于放置在本地堆栈上的字符串变量str
,并且该变量会一直保留,直到您编写的块或函数结束。因此,当您尝试使用cout
stringstream.str()
返回一个临时字符串对象时,其中的const char*
仍然是良好的内存。如果从该(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 std::string& tmp = stringstream.str();
const char* cstr = tmp.c_str();
}
依我看,这是最好的解决办法。不幸的是,它不是很出名。
在这一行中:
const char* cstr2 = ss.str().c_str();
ss.str()
将复制stringstream的内容。当您在同一行上调用c_str()
时,您将引用合法数据,但在该行之后,字符串将被销毁,使您的char*
指向无主内存。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;
}
将更好、更简单。在cstr2
初始化完成后,ss.str()
临时文件将被销毁。因此,当您使用cout
打印它时,与该std::string
临时文件关联的c字符串早就被破坏了,因此,如果它崩溃并断言,您将很幸运,如果它打印垃圾或似乎工作,您将不幸运
const char* cstr2 = ss.str().c_str();
但是,cstr1
指向的C字符串与执行cout
时仍然存在的字符串相关联,因此它正确地打印结果
在下面的代码中,第一个cstr
是正确的(我假设它在实际代码中是cstr1
)。第二个打印与临时字符串对象ss.str()
关联的c字符串。对象在计算其出现的完整表达式时被销毁。完整表达式是整个cout。应该注意的是,复制(如第一个示例中所示)不一定会带来任何开销-如果str()
的实现方式是RVO可以启动的(这很可能),则允许编译器将结果直接构造到tmp
,避免临时性的;而任何现代C++编译器都会在启用优化时这样做。当然,绑定到常量引用解决方案保证不复制,因此可能更可取,但我认为它仍然值得澄清。“当然,绑定到常量引用解决方案保证不复制”您的第一个示例是错误的。c_str()返回的值是瞬态的。在当前报表结束后,不能依赖它。因此,您可以使用它将值传递给函数,但决不能将c_str()的结果赋给局部变量。@litb:从技术上讲,您是正确的。指针在对字符串进行下一次非成本方法调用之前有效。问题是这种用法本身就很危险。可能不是对最初的开发人员(虽然在本例中是这样),但特别是对于后续的维护修复,这种代码变得非常脆弱。如果你想这样做,你应该包装指针的作用域,使它的用法尽可能短(最好是表达式的长度)。@sbi:好的,谢谢,这更清楚了。严格地说,由于上面的代码中没有修改'string str'变量,str.c_str()仍然是完全有效的,但我理解在其他情况下的潜在危险。
std::string resultstr = ss.str();
const char* cstr2 = resultstr.c_str();
const char* cstr2 = ss.str().c_str();
cout << cstr // Prints correctly
<< ss.str().c_str() // Prints correctly
<< cstr2; // Prints correctly (???)
string a = "hello";
string b(a);
assert(a.c_str() == b.c_str());