c_str()を使ってはいけない場面

  • 投稿日:
  • by
  • カテゴリ:



ていうか、char*とstd::stringを混ぜるのが間違ってる気がしますが。


古くはCStringとか。


プリエンプションがありうるなら、c_str()で得たポインタの先は、突然なくなっているかもしれないと常に心配することになります。


分かりやすい例:



string str="hello";
char* p=str.c_str();
str="goodbye";
//これはいいのか?
printf( "%s", p );
//だめにきまってんじゃん

内容を変更したらc_str()の値は変わるものと思わなければなりません。ここではわざとstrの内容を変更しましたが、プリエンプションやタスクスイッチが発生したら、おなじことが起こります。



printf()に渡す瞬間まで確定していれば良い



というのも間違っています。printf()の向こう側でプリエンプションが無いと誰が保証してくれるでしょうか。


ただし、オブジェクトstrがオート変数なら、それはそのコンテキストのスタック上に存在しますから、余計な心配なのです。←中級者さんでもはまる罠。


再入の可能性があったって関係ありません。安全です。


うわっつらのコードレビューで簡単に摘出できそうな例も挙げておきます。


一目見て、だめだと分かる例:



string str;
const char* hoge::getter(){
return str.c_str();
}
void hoge::setter(string& val){
str=val;
}

getter()を呼んで得たポインタはsetter()が呼ばれた途端に使えなくなります。stringをreturnしたくない理由がどこにあるか理解に苦しむところです。



さて、簡単なチェックリストは以下のようになるでしょうか。



  • グローバルなstringオブジェクトのc_str()は原則的に禁止する

  • オート変数って安心だなぁ

  • ある瞬間の中身が見たいだけなら、別のstringオブジェクトにコピーして参照すれば良い


注意しなければならないのは、ここで書いている話はSTLがマルチスレッド対応とかマルチタスク対応とかいっているのとは関係のない話だということです。


マルチスレッド対応STLでも、グローバルなstringオブジェクトのc_str()はいつ内容が化けるか分かったものではないのです。もしかしたら不正なポインタになっているかもしれません。


# 私が知らないだけかもしれない


こざかしいことを考える前に、こう考えたらどうでしょうか。



グローバルなポインタがあるとする。


このポインタは、別のオブジェクトの要素を指している。


このポインタを別のポインタにコピーしてオブジェクトの中身をダンプする関数を作ってみよう。



本質はSTL云々ではなく、ポインタの取り扱い上の基本的な注意と同じなのです。



時折見かけるのは、stringオブジェクトを抱えるクラスのメンバ関数に、排他制御が百花繚乱になっていることです。ここで挙げた基本を抑えれば、そんな無駄なことはしなくてすむかもしれません。