stringstream,string和char*转换混乱

Gra*_*oob 137 c++ memory string stringstream

我的问题可以归结为,字符串从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;
}
Run Code Online (Sandbox Code Playgroud)

stringstream.str().c_str()可以分配给a 的假设const char*导致我花了一段时间追踪的错误.

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

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)
Run Code Online (Sandbox Code Playgroud)

正确打印字符串?

我在Visual Studio 2008中编译.

sbi*_*sbi 195

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

您可以将该临时字符串对象复制到其他字符串对象并从该字符串中获取C字符串:

const std::string tmp = stringstream.str();
const char* cstr = tmp.c_str();
Run Code Online (Sandbox Code Playgroud)

请注意,我创建了临时字符串const,因为对它的任何更改都可能导致它重新分配,从而导致cstr无效.因此,根本不存储调用的结果str()并且cstr仅在完整表达式结束时使用它是更安全的:

use_c_str( stringstream.str().c_str() );
Run Code Online (Sandbox Code Playgroud)

当然,后者可能并不容易,复制可能过于昂贵.你可以做的是将临时绑定到const引用.这会将其生命周期延长到参考的生命周期:

{
  const std::string& tmp = stringstream.str();   
  const char* cstr = tmp.c_str();
}
Run Code Online (Sandbox Code Playgroud)

IMO这是最好的解决方案.不幸的是,它并不是很有名.

  • 应该注意的是,执行副本(如在第一个示例中)不一定会引入任何开销 - 如果`str()`以RVO可以启动的方式实现(很可能),则允许编译器将结果直接构造成`tmp`,省略临时; 任何现代C++编译器都会在启用优化时执行此操作.当然,bind-to-const-reference解决方案保证不复制,所以可能更好 - 但我认为它仍然值得澄清. (13认同)
  • @litb:你在技术上是正确的.指针有效,直到对字符串进行下一个非成本方法调用.问题是使用本质上是危险的.也许不是原始的开发人员(虽然在这种情况下是这样)但特别是对于后续的维护修复,这种代码变得非常脆弱.如果你想这样做,你应该包装指针范围,以便它的使用尽可能短(最好是表达式的长度). (2认同)

Jar*_*aus 13

你正在做的是创造一个临时的.该临时存在于由编译器确定的范围内,因此它足够长以满足其前进的要求.

一旦语句const char* cstr2 = ss.str().c_str();完成,编译器就没有理由保留临时字符串,并且它被销毁,因此你const char *指向freed内存.

您的语句string str(ss.str());意味着临时在构造函数中用于您放在本地堆栈上的string变量str,并且只要您期望就会保留:直到块结束或您编写的函数.因此,const char *当您尝试时,内部仍然是良好的记忆cout.


fbr*_*eto 5

在这一行:

const char* cstr2 = ss.str().c_str();
Run Code Online (Sandbox Code Playgroud)

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


Kla*_*aim 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;
}
Run Code Online (Sandbox Code Playgroud)

这样你就能获得更长时间的字符串.

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

 std::string resultstr = ss.str();
 const char* cstr2 = resultstr.c_str();
Run Code Online (Sandbox Code Playgroud)

会更好更简单.


Joh*_*itb 5

ss.str()初始化cstr2完成后,临时文件将被销毁.所以当你用它打印时cout,与那个std::string临时关联的c字符串很久就会被破坏,因此如果它崩溃并断言你会很幸运,如果它打印垃圾或看起来有效则不幸运.

const char* cstr2 = ss.str().c_str();
Run Code Online (Sandbox Code Playgroud)

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

在下面的代码中,第一个cstr是正确的(我假设它是cstr1在实际代码中?).第二个打印与临时字符串对象关联的c字符串ss.str().在评估出现的完整表达式结束时,对象将被销毁.full-expression是整个cout << ...表达式 - 因此在输出c-string时,关联的字符串对象仍然存在.因为cstr2- 成功是纯粹的坏事.它最有可能在内部为新临时选择相同的存储位置,它已经为临时用于初始化的临时选择cstr2.它也可能崩溃.

cout << cstr            // Prints correctly
    << ss.str().c_str() // Prints correctly
    << cstr2;           // Prints correctly (???)
Run Code Online (Sandbox Code Playgroud)

返回c_str()通常只指向内部字符串缓冲区 - 但这不是必需的.如果字符串的内部实现不是连续的,那么字符串可以组成一个缓冲区(这很可能 - 但在下一个C++标准中,字符串需要连续存储).

在GCC中,字符串使用引用计数和写时复制.因此,您会发现以下内容成立(至少在我的GCC版本中)

string a = "hello";
string b(a);
assert(a.c_str() == b.c_str());
Run Code Online (Sandbox Code Playgroud)

这两个字符串共享相同的缓冲区.当您更改其中一个时,将复制缓冲区,每个缓冲区将保留其单独的副本.但是,其他字符串实现会有所不同.