stringstream、string、char *変換の混乱


141

私の質問は要約することができます。文字列はstringstream.str().c_str()メモリ内のどこから返されますか。なぜそれをaに割り当てることができないの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でコンパイルしています。

回答:


201

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が最良のソリューションです。残念ながらあまり知られていません。


13
(最初の例のように)コピーを実行しても必ずしもオーバーヘッドが発生するわけではないことに注意してくださいstr()。RVOが実行できるように実装されている場合(これは非常に可能性があります)、コンパイラは結果を直接作成できます。にtmp、一時的なものを除外します。また、最新のC ++コンパイラは、最適化が有効になっている場合にそうします。もちろん、bind-to-const-referenceソリューションはコピーなしを保証するので、望ましいかもしれませんが、それでも明確にする価値があると思いました。
Pavel Minaev

1
「もちろん、binst-to-const-referenceソリューションはコピー不可を保証します」<-それは保証しません。C ++ 03では、コピーコンストラクターがアクセス可能である必要があり、実装は初期化子をコピーして参照をコピーにバインドできます。
Johannes Schaub-litb

1
最初の例は間違っています。c_str()が返す値は一時的なものです。現在のステートメントの終了後は信頼できません。したがって、これを使用して値を関数に渡すことができますが、c_str()の結果をローカル変数に割り当てないでください。
マーティンヨーク

2
@litb:あなたは技術的に正しいです。ポインタは、文字列に対する次の非コストメソッドの呼び出しまで有効です。問題は、使用が本質的に危険であることです。おそらく元の開発者ではなく(この場合はそうでした)、特にその後のメンテナンス修正では、この種のコードは非常に壊れやすくなります。これを行う場合は、ポインタのスコープをラップして、使用量をできるだけ短くする必要があります(式の長さが最適です)。
マーティンヨーク

1
@sbi:わかりました。ありがとうございます。しかし厳密に言えば、上のコードでは 'string str' varは変更されていないため、str.c_str()は完全に有効なままですが、他の場合の潜在的な危険性を高く評価しています。
ウィリアムナイト

13

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

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

ステートメントstring str(ss.str());は、ローカルスタックに配置したstring変数のコンストラクターで一時変数が使用されstr、ブロックが終了するまで、または記述した関数が終了するまで、期待したとおりにとどまります。したがって、const char *を試しても、メモリ内は良いメモリですcout


6

この行では:

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

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


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();

より良く、より簡単になるでしょう。


5

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

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

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

次のコードでは、最初のコードcstrが正しいです(cstr1実際のコードにあると思いますか?)。2番目は、一時的な文字列オブジェクトに関連付けられたc-stringを出力しますss.str()。オブジェクトは、それが出現する完全式の評価の最後に破棄されます。full-expressionは式全体です。cout << ...したがって、c-stringが出力されている間、関連付けられたstringオブジェクトはまだ存在しています。なぜなら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つを変更すると、バッファーがコピーされ、それぞれが個別のコピーを保持します。ただし、他の文字列の実装では、処理が異なります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.