メモリウィンドウを使ったデバッグ法
VCにあるメモリウィンドウですが、使わなくてもプログラムは作れますが、知っていると役に立つかもしれません。
なのでちょっと入門用としてまとめてみました。今回は製作やっていない代わりこっちをやっていました。
とりあえずこいつを見てくれ。どう思う?
void* pAlloc = malloc( 1024 ); memset( pAlloc, 0, 1024 );// 1 int* p = (int*)pAlloc;// 2 *p = 0xff; p += 1;// 3 *p = 0xfe; assert(p); // 4
すごく。。。大きいです。。。無駄に。(コードの説明はしないからね。)
上記のコードのコメントにある 1 でブレークをかけます。
その時の pAlloc 変数をウォッチで見ると、次のように
値が 0x00dc9ad8 になっています。
これは確保した領域の先頭アドレスになります。
メモリを覗く
上記のアドレスをメモリウィンドウのアドレスにコピペしてエンターを押してください。
そうすると次のようになります。
初期化される前なので、値には cd が埋め尽くされています。
次にコードを 2 まで進めます。
もちろんメモリ内は次のように0になります。*1
次にコードを 3 まで進めます。
そうするとメモリは次のように値が入れられます。
この先頭にある値 ff は、コードで言う
*p = 0xff;
の ff になります。
次にコードを 4 まで進めます。
そうするとメモリは次のように値が入れられます。
先頭アドレスから 4バイト先に fe が代入されました。
これはコードで言う
*p = 0xfe;
になります。
疑問
その前になぜ 4バイト先なのかというと、変数 p は int 型です。
int 型は 4バイトです。なので、変数 p に 1を加算すると、4バイト進みます。
もしこれが char 型の場合は 1バイトずつ進みます。
クラスの場合はどうなるの?
次のようなクラスを宣言し、
class Test { int a; short b[2]; char c[4]; float d; public: Test() : a(0xff), d(1.0f){ b[0] = 0xfe; b[1] = 0xfd; c[0] = 'u'; c[1] = 'n'; c[2] = 'k'; c[3] = 'o'; } void f(){ a = 552; } }; Test t; Test* p = &t; assert(p);
インスタンスを作成、アドレス先のメモリを見ると次のようになります。
メンバ変数は、先頭から順に配置されているのが分かります。*2
ちなみに仮想関数があると
仮想関数がある場合は、次のようになります。
メモリの先頭4バイトに何か入った後、2行目から先程のメンバ変数が配置されています。
この先頭4バイトは、vテーブルという仮想関数へのアドレスになります。
ウォッチで見ると分かります。
_vfptr という変数です。
おそらく virtual function pointer の略でしょう。
ちなみに継承していると
次のコードのように継承している場合は、次のようになります。
class T { char C[4]; public: T(){ C[0] = 'U'; C[1] = 'N'; C[2] = 'K'; C[3] = 'O'; } }; class Test : public T { int a; short b[2]; char c[4]; float d; public: Test() : a(0xff), d(1.0f){ b[0] = 0xfe; b[1] = 0xfd; c[0] = 'u'; c[1] = 'n'; c[2] = 'k'; c[3] = 'o'; } void f(){ a = 552; } }; Test t; Test* p = &t; assert(p);
メモリの一行目は先程説明した仮想関数のアドレスです。
二行目は親クラスにメンバ変数です。
三行目から派生先のメンバ変数が配置されています。
危険なコード
Test t; memset( &t, 0, sizeof(Test) );
または
class D : public Test { D(){ memset( this, 0, sizeof(D) ); } };
などと書くと、vテーブルが吹き飛びます。
まとめ
なんとなく理解できたでしょうか?
日頃ただクラスや変数を宣言しているだけでは、どのようにメモリに乗っているかは分からないと思います。
このように一度メモリの中身を覗いて見ると、高速化やタブーなどを意識できると思います。
あと少しだけですが、知らない人に自慢できます。