代码之家  ›  专栏  ›  技术社区  ›  Graphics Noob

stringstream、string和char*转换混淆

  •  131
  • Graphics Noob  · 技术社区  · 15 年前

    我的问题可以归结为,弦是从哪里返回的 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()。 可以分配给 常量字符 导致了一个花了我一段时间才找到的错误。

    对于奖励积分,有人能解释为什么要替换 cout 语句与

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

    正确打印字符串?

    我正在Visual Studio 2008中编译。

    5 回复  |  直到 8 年前
        1
  •  183
  •   Yinon Ehrlich    8 年前

    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 std::string& tmp = stringstream.str();   
      const char* cstr = tmp.c_str();
    }
    

    我觉得这是最好的解决办法。不幸的是,它并不为人所知。

        2
  •  13
  •   Jared Oberhaus    15 年前

    你所做的就是创造一个临时的。这个临时变量存在于编译器确定的范围内,这样它的长度就足以满足它要去哪里的需求。

    一旦声明 const char* cstr2 = ss.str().c_str(); 完成后,编译器看不到保留临时字符串的任何理由,它将被销毁,因此 const char * 指向空闲的内存。

    你的声明 string str(ss.str()); 表示临时对象在构造函数中用于 string 变量 str 你已经放在本地堆栈上了,并且它会一直保持在你所期望的范围内:直到你所写的块或函数的末尾。因此 常量字符 当你尝试 cout .

        3
  •  5
  •   fbrereto    15 年前

    在这一行中:

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

    ss.str() 将成为 复制 字符串流的内容。当你打电话 c_str() 在同一行中,您将引用合法的数据,但在该行之后,字符串将被销毁,从而使您的 char* 指向无主的记忆。

        4
  •  5
  •   Klaim    15 年前

    ss.str()返回的std::string对象是一个临时对象,其生命周期将限制在表达式中。因此,在没有垃圾桶的情况下,不能将指针分配给临时对象。

    现在,有一个例外:如果您使用常量引用来获取临时对象,那么在更大的生命周期内使用它是合法的。例如,您应该这样做:

    #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
  •  5
  •   Johannes Schaub - litb    15 年前

    这个 ss.str() 临时在初始化后被销毁 cstr2 是完整的。所以当你打印的时候 cout ,与之关联的C字符串 std::string 暂时的已经被摧毁很久了,因此如果它崩溃并断言,你将很幸运;如果它打印垃圾或看起来确实工作,你将不幸运。

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

    C字符串 cstr1 但是,指向与执行该操作时仍存在的字符串相关联。 库特 -所以它正确地打印结果。

    在下面的代码中,第一个 cstr 是正确的(我想是 CSTR1 在真正的代码中?)。第二个打印与临时字符串对象关联的C字符串。 S.() . 在计算对象出现的完整表达式的末尾,将销毁该对象。完整表达式是整个 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());
    

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