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