ビジネス WinDbgアプリケーション開発技術 イメージロード(ユーザモードとカーネルモード)


ch->data()の意味

 それではいつものように次のC++ソースコードを見てみましょう。
#include <iostream>

int main()
{
  const std::string ch0 = "Typical: I don't know anything about programming!\n";
  const std::string ch ("Simplified form: I don't know anything about programming!\n");
  const std::string* ch1 =  new std::string ("Pointer form: I don't know anything about programming!\n");

  std::cout << "Popular: I don't know anything about programming!" << std::endl;
  std::printf(ch0.c_str());

  std::cout << ch.data();
  std::cout << ch1->data();
  std::cout << ch.c_str();
  std::cout << ch1->c_str();

  std::cout.write(ch.c_str(), ch.size());
  std::cout.write(ch1->c_str(), ch1->size());

 delete ch1;
  return int(0);
}
 私たちはこのC++ソースコードから次のような印象を受けます。すでに説明済みの項目は、打ち消されています。

印象1:とにかく難しい!
印象2:Cのソースコードとどこか似ている
印象3:#includeに続くiosteamという意味が分からない
印象4:stdio.hがないにもかかわらず、printf関数が呼び出されていることが不思議だ!
印象5:std::coutのstdの意味が分からない
印象6:std::coutの「::」の意味が分からない
印象7:std::coutのcoutの意味が分からない
印象8:「<<」の意味が分からない
印象9:std::endlのendlの意味が分からない
印象10:「std::string ch」のstringの意味が分からない
印象11:「std::string ch」のchの意味が分からない
印象12:ch.data()の意味が分からない
印象13:ch.c_str()の意味が分からない

印象14:std::cout.writeの意味が分からない
印象15:ch.size()の意味が分からない
印象16:std::string*の「*」の意味が分からない
印象17:std::string* ch1の「ch1」の意味が分からない
印象18:ch1->data()の意味が分からない
印象19:ch1->c_str()の意味が分からない
印象20:ch1->size()の意味が分からない

 今回は、前回コードと深い関係にある、「印象18:ch1->data()の意味が分からない」の意味を考えてみたいと思います。次のVC++2003コンパイラ出力情報をご覧いただきましょう。

   12: 	std::cout << ch.data();
00401121 8B F4            mov         esi,esp 
00401123 8D 4D AC         lea         ecx,[ch] (0x0012ff14)
00401126 FF 15 78 92 40 00 call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::data (409278h)] 
0040112C 3B F4            cmp         esi,esp 
0040112E E8 6D 03 00 00   call        _RTC_CheckEsp (4014A0h) 
00401133 8B F4            mov         esi,esp 
00401135 50               push        eax  
00401136 8B 0D 68 92 40 00 mov         ecx,dword ptr [__imp_std::cout (409268h)] 
0040113C 51               push        ecx  
0040113D FF 15 6C 92 40 00 call        dword ptr [__imp_std::operator<<<std::char_traits<char> > (40926Ch)] 
00401143 83 C4 08         add         esp,8 
00401146 3B F4            cmp         esi,esp 
00401148 E8 53 03 00 00   call        _RTC_CheckEsp (4014A0h)


   13: 	std::cout << ch1->data();
0040114D 8B F4            mov         esi,esp 
0040114F 8B 4D A4         mov         ecx,dword ptr [ch1](0x00376048) 
00401152 FF 15 78 92 40 00 call        dword ptr [__imp_std::basic_string<char,std::char_traits<char>,std::allocator<char> >::data (409278h)] 
00401158 3B F4            cmp         esi,esp 
0040115A E8 41 03 00 00   call        _RTC_CheckEsp (4014A0h) 
0040115F 8B F4            mov         esi,esp 
00401161 50               push        eax  
00401162 8B 15 68 92 40 00 mov         edx,dword ptr [__imp_std::cout (409268h)] 
00401168 52               push        edx  
00401169 FF 15 6C 92 40 00 call        dword ptr [__imp_std::operator<<<std::char_traits<char> > (40926Ch)] 
0040116F 83 C4 08         add         esp,8 
00401172 3B F4            cmp         esi,esp 
00401174 E8 27 03 00 00   call        _RTC_CheckEsp (4014A0h) 

 赤字部分を見ると、筆者の環境では、「ch」はアドレス0x0012ff14、「ch1」はアドレス0x00376048を、それぞれ指していることが分かります。「ch」の場合、そのアドレスはLEA(Load Effective Address)という命令で取り出され、「ch1」の場合には、MOV(転送)命令が使われています。一般論で言えば、この場合のMOV命令はメモリとレジスタ間のデータ転送を発生させますから、パフォーマンスが若干低下するといえます。筆者が調べた結論(つまり、簡単なステップインデバッグ分析の結果)だけをお伝えすると、(VS.NET 2003)コンパイラライタは「ch.data()」と「ch1->data()」の相違を次のように表現しています。
ch.c_str(this*, ch);  // ch.data()。

ch1->c_str(this*, ch1*); // ch1->data()。
 ご覧のように、「data()」メソッドは、内部で「c_str()」メソッドを再利用しています。しかし、受け取る第2引数の「型」が異なっています。前者は有効アドレス、後者は、強いて言えば、次のコードが示しているように、"別のアドレスを指すアドレス(つまり、そのままでは有効なアドレスとして使えない!)"、ということになります。
12:   printf("%d %d", a1, *d1);
00401143 8B F4            mov         esi,esp 
00401145 8B 45 E8         mov         eax,dword ptr [d1]eaxはそのまま使えない! 
00401148 8B 08            mov         ecx,dword ptr [eax] 
0040114A 51               push        ecx  
0040114B 8B 55 EC         mov         edx,dword ptr [a1] 
0040114E 52               push        edx edxはそのまま使える! 
0040114F 68 4C 66 40 00   push        offset ___xi_z+138h (40664Ch) 
00401154 FF 15 0C 94 40 00 call        dword ptr [__imp__printf (40940Ch)] 
0040115A 83 C4 0C         add         esp,0Ch 
0040115D 3B F4            cmp         esi,esp 
0040115F E8 DC 09 00 00   call        _RTC_CheckEsp (401B40h) 

 なお、先のソースコード内の「ch」は不要になれば、「自然に」、あるいは、「自動的に」解放されるところから、「自然変数」や「自動変数」と呼ばれています。一方、「ch1」は、「new」という演算子で私たちが明示的に確保するメモリブロック(文字列を格納している)の先頭アドレスを指すポインタです。このため、厳密には、不要になった時点で、「delete ch1;」というコードを私たちが明示的に実行させ、解放する必要があります。これもよくよく考えてみれば当たり前のことで、決して難しいことではありません。MOV命令はデータを転送する命令であり、メモリとレジスタ間の、一種の「データ通信」です。データ通信が終了すれば、通信の両端を解放するのが流儀です。

前へ | 次へ




 WinDbg入門  ホーム


Copyright©豊田孝 2004- 2009
本日は2009-01-06です。