繁体   English   中英

stringstream、string 和 char* 转换混淆

[英]stringstream, string, and char* conversion confusion

我的问题可以归结为,从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 with语句

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

正确打印字符串?

我正在 Visual Studio 2008 中编译。

stringstream.str()返回一个临时字符串对象,该对象在完整表达式结束时被销毁。 如果你从中得到一个指向 C 字符串的指针( stringstream.str().c_str() ),它将指向一个在语句结束处被删除的字符串。 这就是您的代码打印垃圾的原因。

您可以将该临时字符串对象复制到其他字符串对象,并从该对象中获取 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 这是最好的解决方案。 不幸的是,它不是很出名。

你正在做的是创建一个临时的。 该临时存在于由编译器确定的范围内,因此它的长度足以满足它要去的地方的要求。

只要声明const char* cstr2 = ss.str().c_str(); 完成后,编译器认为没有理由保留临时字符串,它已被销毁,因此您的const char *指向已释放的内存。

你的语句string str(ss.str()); 意味着临时变量在构造函数中用于您放在本地堆栈上的string变量str ,并且只要您期望它就会一直存在:直到块的末尾,或者您编写的函数。 因此,当您尝试cout时,其中的const char *仍然是很好的记忆。

在这一行:

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

这样你就可以得到更长的时间。

现在,您必须知道有一种称为 RVO 的优化,它说如果编译器通过函数调用看到初始化并且该函数返回一个临时值,它不会进行复制,而只是使分配的值成为临时值. 这样你就不需要实际使用引用,只有当你想确保它不会复制它是必要的。 这样做:

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

会更好更简单。

ss.str()临时文件在cstr2初始化完成后被销毁。 因此,当您使用cout打印它时,与该std::string临时相关联的 c 字符串早已被销毁,因此,如果它崩溃并断言,您将很幸运,而如果它打印垃圾或看起来确实如此,则不走运工作。

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

但是, cstr1指向的 C 字符串与执行cout时仍然存在的字符串相关联 - 因此它可以正确打印结果。

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

这两个字符串在这里共享相同的缓冲区。 当您更改其中之一时,缓冲区将被复制并且每个缓冲区都将保存其单独的副本。 但是,其他字符串实现做的事情不同。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM