簡體   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