私の質問を要約すると、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でコンパイルしています。
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が最適なソリューションです。残念ながら、あまり知られていません。
あなたがしているのは一時的なものを作成することです。その一時的なものは、コンパイラーによって決定されたスコープ内に存在するので、どこに行くのかの要件を満たすのに十分な長さです。
ステートメント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の内容のcopyを作成します。同じ行でc_str()
を呼び出すと、正当なデータを参照することになりますが、その行の後、文字列は破棄され、char*
が未所有のメモリを指すようになります。
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();
より良く、より簡単になります。
ss.str()
テンポラリは、cstr2
の初期化が完了すると破棄されます。したがって、cout
で印刷すると、そのstd::string
temporaryに関連付けられたc-stringは長い間破壊されているため、クラッシュしてアサートした場合はラッキーになりますが、印刷した場合はラッキーではありませんゴミまたは動作するように見えます。
const char* cstr2 = ss.str().c_str();
ただし、cstr1
が指すC文字列は、cout
を実行した時点でまだ存在する文字列に関連付けられているため、結果が正しく出力されます。
次のコードでは、最初のcstr
が正しい(実際のコードではcstr1
であると仮定しますか?)。 2番目は、一時文字列オブジェクトss.str()
に関連付けられたC文字列を出力します。オブジェクトは、表示される完全な式の評価の最後に破棄されます。完全な式はcout << ...
式全体です。したがって、c-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つの文字列は同じバッファを共有します。それらのいずれかを変更すると、バッファーがコピーされ、それぞれが個別のコピーを保持します。ただし、他の文字列実装では異なることが行われます。