stringstream,string和char *转换混乱


141

我的问题可以归结为,从stringstream.str().c_str()实时内存返回的字符串在哪里,为什么不能将其分配给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*导致了一个错误,导致我花了一些时间来追踪。

对于奖励积分,任何人都可以解释为什么将cout语句替换为

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

正确打印字符串?

我正在Visual Studio 2008中进行编译。

Answers:


201

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是最好的解决方案。不幸的是,它不是很知名。


13
应该注意的是,进行复制(如您的第一个示例中所示)并不一定会带来任何开销-如果str()以RVO可以踢入的方式(非常有可能)实现,则允许编译器直接构造结果进入tmp,消除临时;启用优化后,任何现代C ++编译器都将这样做。当然,bind-to-const-reference解决方案可确保不进行复制,因此可能更可取-但我认为仍然值得澄清。
帕维尔·米纳夫

1
“当然,bind-to-const-reference解决方案保证不进行复制”。在C ++ 03中,需要可访问副本构造函数,并且允许实现复制初始化程序并将引用绑定到副本。
Johannes Schaub-litb

1
您的第一个示例是错误的。c_str()返回的值是瞬态的。当前语句结束后不能再依赖它。因此,您可以使用它将值传递给函数,但永远不要将c_str()的结果分配给局部变量。
马丁·约克2009年

2
@litb:从技术上来说您是正确的。该指针在字符串上的下一个非开销方法调用之前一直有效。问题在于使用具有固有的危险性。也许不是针对原始开发人员的(尽管在本例中是这样),但尤其是针对后续的维护修补程序,此类代码变得极为脆弱。如果要执行此操作,则应包装指针范围,以使它的用法尽可能短(最好是表达式的长度)。
马丁·约克2009年

1
@sbi:好的,谢谢。但是严格来说,由于在上面的代码中'string str'var没有被修改,因此str.c_str()仍然是完全有效的,但是我理解在其他情况下的潜在危险。
威廉·奈特

13

您正在做的是创建一个临时文件。该临时文件存在于由编译器确定的范围内,因此它足够长,可以满足其运行的要求。

该语句const char* cstr2 = ss.str().c_str();完成后,编译器将立即发现没有理由保留临时字符串,并且该临时字符串已被销毁,因此您const char *指向的是释放的内存。

您的声明string str(ss.str());意味着临时string变量str将在构造函数中用于您放置在本地堆栈中的变量,并且该变量的存在时间与您期望的一样长:直到块或您编写的函数结束。因此,const char *当您尝试使用时,内部仍然是不错的记忆cout


6

在这一行:

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

ss.str()复制 stringstream的内容。当您c_str()在同一行上调用时,将引用合法数据,但是在该行之后,字符串将被销毁,从而使您char*指向未拥有的内存。


5

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但是,指向的C字符串与执行操作时仍然存在的字符串相关联cout-因此它可以正确打印结果。

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

这两个字符串在此处共享相同的缓冲区。更改其中一个时,缓冲区将被复制,并且每个缓冲区将保留其单独的副本。但是,其他字符串实现的功能有所不同。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.