150

私の質問は要約すると、メモリ内のライブから返された文字列はどこにあり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 (???)

文字列を正しく印刷しますか?

VisualStudio2008でコンパイルしています。

4

5 に答える 5

212

stringstream.str()完全な式の最後で破棄される一時的な文字列オブジェクトを返します。その(stringstream.str().c_str())からC文字列へのポインタを取得すると、ステートメントが終了する場所で削除された文字列を指します。これが、コードがガベージを出力する理由です。

その一時的な文字列オブジェクトを他の文字列オブジェクトにコピーして、そのオブジェクトから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。残念ながら、それはあまりよく知られていません。

于 2009-09-03T16:25:21.457 に答える
14

あなたがしているのは、一時的なものを作成することです。その一時的なものは、コンパイラーによって決定されたスコープ内に存在するため、それがどこに行くのかという要件を満たすのに十分な長さです。

ステートメントconst char* cstr2 = ss.str().c_str();が完了するとすぐに、コンパイラは一時的な文字列を保持する理由を認識せず、それが破棄されるため、const char *解放されたメモリを指していることになります。

あなたのステートメントは、ローカルスタックに置いた変数string str(ss.str());のコンストラクターで一時的なものが使用され、ブロックの終わりまたは作成した関数まで、期待する限り存続することを意味します。したがって、を試してみると、内部はまだ良い記憶です。stringstrconst char *cout

于 2009-09-03T16:25:18.693 に答える
6

この行で:

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

ss.str()stringstreamの内容のコピーを作成します。同じ行を呼び出すc_str()と、正当なデータを参照することになりますが、その行の後、文字列は破棄され、char*所有されていないメモリを指すようになります。

于 2009-09-03T16:27:46.100 に答える
5

ss.str()によって返されるstd :: stringオブジェクトは、式に制限された有効期間を持つ一時オブジェクトです。したがって、ゴミ箱を取得せずに一時オブジェクトにポインタを割り当てることはできません。

ここで、1つの例外があります。一時オブジェクトを取得するために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();

より良く、より簡単になります。

于 2009-09-03T16:30:28.107 に答える
5

の初期化が完了ss.str()すると、一時は破棄されます。cstr2したがって、を使用して印刷するとcout、その一時に関連付けられていたc-stringstd::stringは長い間破棄されてきたため、クラッシュしてアサートされた場合は幸運であり、ガベージを印刷したり機能しているように見える場合は幸運ではありません。

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

cstr1ただし、を指すC文字列は、実行時にまだ存在している文字列に関連付けられているcoutため、結果が正しく出力されます。

次のコードでは、最初のコードcstrが正しいです(cstr1実際のコードにあると思いますか?)。2つ目は、一時文字列オブジェクトに関連付けられたc文字列を出力しますss.str()。オブジェクトは、それが表示される完全な式の評価の最後に破棄されます。完全式は式全体です。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());

ここでは、2つの文字列が同じバッファを共有しています。それらの1つを変更すると、バッファーがコピーされ、それぞれが個別のコピーを保持します。ただし、他の文字列の実装では、動作が異なります。

于 2009-09-03T16:33:34.373 に答える