stringstream、string 和 char* 转换混淆

Posted

技术标签:

【中文标题】stringstream、string 和 char* 转换混淆【英文标题】:stringstream, string, and char* conversion confusion 【发布时间】:2010-11-25 08:36:35 【问题描述】:

我的问题可以归结为,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 中编译。

【问题讨论】:

【参考方案1】:

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

【讨论】:

应该注意的是,进行复制(如在您的第一个示例中)不一定会引入任何开销 - 如果 str() 以 RVO 可以启动的方式实现(这很可能),允许编译器将结果直接构造成tmp,省略临时;当启用优化时,任何现代 C++ 编译器都会这样做。当然,bind-to-const-reference 解决方案保证不复制,所以可能更可取 - 但我认为它仍然值得澄清。 “当然,bind-to-const-reference 解决方案保证不复制” 你的第一个例子是错误的。 c_str() 返回的值是瞬态的。在当前语句结束后不能依赖它。因此,您可以使用它将值传递给函数,但绝不应将 c_str() 的结果分配给局部变量。 @litb:您在技术上是正确的。指针在字符串上的下一个非成本方法调用之前有效。问题是这种用法本质上是危险的。也许不是对最初的开发人员(尽管在这种情况下是这样),但尤其是对后续的维护修复来说,这种代码变得非常脆弱。如果你想这样做,你应该包装指针范围,以便它的使用尽可能短(最好是表达式的长度)。 @sbi:好的,谢谢,这更清楚了。不过,严格来说,由于上面的代码中没有修改 'string str' var,所以 str.c_str() 仍然完全有效,但我理解其他情况下的潜在危险。【参考方案2】:

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

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

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

【讨论】:

【参考方案3】:

在这一行:

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

ss.str() 将制作字符串流内容的副本。当您在同一行调用 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】:

cstr2 的初始化完成后,ss.str() 临时被销毁。因此,当您使用 cout 打印它时,与该 std::string 临时关联的 c 字符串早已被破坏,因此如果它崩溃并断言,您会很幸运,如果它打印垃圾或确实出现,则不会幸运去工作。

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

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

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

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

【讨论】:

以上是关于stringstream、string 和 char* 转换混淆的主要内容,如果未能解决你的问题,请参考以下文章

stringstream的用法

sstream头文件-getline 函数 和 stringstream函数 和string的常见用法

从 stringstream 到 string 的转换会删除 '=' 字符

stringstream的用法(转)

stringstream,ostringstream,stringstream

int,char,string,三者相互转换(stringstream)