2009-09-03 87 views
120

我的问题可以归结为,从stringstream.str().c_str()返回的字符串在哪里存在于内存中,为什么不能将它分配给const char*stringstream,string和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()能分到导致我花了一段时间来跟踪bug一个const char*假设。

加分,任何人都可以解释为什么与

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

正确打印字符串替换cout声明?

我编译Visual Studio 2008中

回答

172

stringstream.str()返回一个在完整表达式结束时被销毁的临时字符串对象。如果从该指针(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引用。这将延长其使用期限到参考的寿命:

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

IMO是最好的解决方案。不幸的是它不是很知名。

+12

需要注意的是,做一个拷贝(就像在你的第一个例子中一样)并不一定会引入任何开销 - 如果'str()'以RVO可以踢入的方式实现(很有可能),那么编译器被允许直接将结果构建到'tmp'中,从而消除临时的;任何现代C++编译器都会在启用优化时执行此操作。当然,绑定到常量引用的解决方案可以保证不会复制,因此可能更可取 - 但我认为这仍然值得澄清。 – 2009-09-03 16:34:32

+1

“当然,绑定到常量引用的解决方案保证没有复制”< - 它没有。在C++ 03中,复制构造函数需要可访问,并且允许实现复制初始化程序并将引用绑定到副本。 – 2009-09-03 16:38:01

+1

你的第一个例子是错误的。由c_str()返回的值是瞬态的。在当前声明结束后不能依赖它。因此,您可以使用它将值传递给函数,但不应将c_str()的结果分配给局部变量。 – 2009-09-03 20:03:26

12

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

只要陈述const char* cstr2 = ss.str().c_str();完成,编译器看不到任何理由保留临时字符串,并且它被销毁,因此您的const char *指向free'd内存。

你的说法string str(ss.str());指暂时在构造函数中,你已经把本地栈上的string变量str使用,只要你想到的是保持左右:直到块结束,或你写的功能。因此,当您尝试cout时,内部的const char *仍然是良好的内存。

5

在这一行:

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

ss.str()会让副本的stringstream的内容。当您在同一行上呼叫c_str()时,您将引用合法数据,但在该行之后,字符串将被销毁,使您的char*指向无主存储器。

4

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

这样你得到的字符串更长的时间。

现在,你必须知道有一种称为RVO的优化,它表示如果编译器通过函数调用看到一个初始化,并且该函数返回一个临时的,它将不会执行副本,而只是分配给它价值是暂时的。这样你就不需要真正使用引用,只有在你确定它不会复制时才需要引用。这样做:

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

会更好,更简单。

5

ss.str()暂时在cstr2初始化完成后被销毁。因此,当您用cout打印时,与该std::string临时关联的c字符串早已失效,因此,如果它崩溃并断言,您将很幸运,如果它打印垃圾或确实似乎正常工作,则不幸运。

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

其中cstr1点,然而,与仍然存在的时候你做的cout字符串相关的C-字符串 - 因此它正确地打印出结果。

在下面的代码中,第一个cstr是正确的(我假设它是cstr1在真实的代码?)。第二个打印与临时字符串对象ss.str()关联的c字符串。该对象在评估其出现的完整表达式结束时被销毁。完整表达式是整个cout << ...表达式 - 所以在输出c字符串时,关联的字符串对象仍然存在。对于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()); 

这两个字符串在这里共享相同的缓冲区。在您更改其中一个时,缓冲区将被复制,并且每个缓冲区都将保存其单独的副本。尽管如此,其他字符串实现却有所不同。