次のプログラムをアセンブルするといい。
配列の各次元の要素数を変えてみると、プログラムが興味深く変化する。
#include<stdio.h> int main(void){ int i = 1, j = 2, k = 3; char a[10], b[10][10], c[10][10][10]; a[i] = 0xff; b[i][j] = 0xff; c[i][j][k] = 0xff; return 0; }
例えば、上の例だとアセンブリは次のようになる。
# mainの前処理がある # レジスタの退避 8049095: 55 push %ebp 8049096: 89 e5 mov %esp,%ebp 8049098: 53 push %ebx 8049099: 51 push %ecx # ローカル変数の確保 804909a: 81 ec 70 04 00 00 sub $0x470,%esp # i, j, kの初期化 (i = ebp - 0xc, j = ebp - 0x10, k = ebp - 0x14) 80490a0: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp) 80490a7: c7 45 f0 02 00 00 00 movl $0x2,-0x10(%ebp) 80490ae: c7 45 ec 03 00 00 00 movl $0x3,-0x14(%ebp) # ebp - 0x1e = 配列aの先頭アドレス、ebp - 0xc = iのアドレス # つまり、movb $0xff, (%eax)ではa[i] = 0xffを行っている 80490b5: 8d 55 e2 lea -0x1e(%ebp),%edx 80490b8: 8b 45 f4 mov -0xc(%ebp),%eax 80490bb: 01 d0 add %edx,%eax 80490bd: c6 00 ff movb $0xff,(%eax) # i * 10 → eax 80490c0: 8b 55 f4 mov -0xc(%ebp),%edx 80490c3: 89 d0 mov %edx,%eax 80490c5: c1 e0 02 shl $0x2,%eax 80490c8: 01 d0 add %edx,%eax 80490ca: 01 c0 add %eax,%eax # ebp - 0x8 = bの疑似先頭アドレス 80490cc: 8d 5d f8 lea -0x8(%ebp),%ebx # bの疑似先頭アドレス + i * 10 → edx 80490cf: 8d 14 03 lea (%ebx,%eax,1),%edx # ebp - 0x10 = j → eax 80490d2: 8b 45 f0 mov -0x10(%ebp),%eax # bの疑似先頭アドレス + i * 10 + j → eax 80490d5: 01 d0 add %edx,%eax # 疑似アドレスを先頭アドレスに変換 80490d7: 83 e8 7a sub $0x7a,%eax # b[i][j] = 0xff; 80490da: c6 00 ff movb $0xff,(%eax) # j → edx 80490dd: 8b 55 f0 mov -0x10(%ebp),%edx # i → ecx 80490e0: 8b 4d f4 mov -0xc(%ebp),%ecx # j * 10 → eax 80490e3: 89 d0 mov %edx,%eax 80490e5: c1 e0 02 shl $0x2,%eax 80490e8: 01 d0 add %edx,%eax 80490ea: 01 c0 add %eax,%eax # i * 100 → edx 80490ec: 6b d1 64 imul $0x64,%ecx,%edx # i * 100 + j * 10 → eax 80490ef: 01 d0 add %edx,%eax # ebp - 0x8 = cの疑似先頭アドレス 80490f1: 8d 5d f8 lea -0x8(%ebp),%ebx # cの疑似先頭アドレス + i * 100 + j * 10 → edx 80490f4: 8d 14 03 lea (%ebx,%eax,1),%edx # k → eax 80490f7: 8b 45 ec mov -0x14(%ebp),%eax # cの疑似先頭アドレス + i * 100 + j * 10 + k → eax 80490fa: 01 d0 add %edx,%eax # 疑似アドレスを正規のアドレスに変換 80490fc: 2d 62 04 00 00 sub $0x462,%eax # c[i][j][k] = 0xff 8049101: c6 00 ff movb $0xff,(%eax) # プログラムはまだまだ続く...
このソースからは、Cのポインタ演算がどんな機械語になるのかがよく分かる。
ポインタの型によってポインタ演算時に加算される値が異なるが、それはiやjを10倍、100倍している部分が表している。
もう一つ同様のコードを見ておこう。
今度はcharをintに直したものだ。
#include<stdio.h> int main(void){ int i = 1, j = 2, k = 3; int a[10], b[10][10], c[10][10][10]; a[i] = 0xff; b[i][j] = 0xff; c[i][j][k] = 0xff; return 0; }
アセンブラコードも貼っておく。
同様に読めるので読むと面白いかもしれない。
# mainの前処理がある # レジスタの退避 8049095: 55 push %ebp 8049096: 89 e5 mov %esp,%ebp 8049098: 51 push %ecx # ローカル変数の確保 8049099: 81 ec 74 11 00 00 sub $0x1174,%esp # ebp - 0xc = i, ebp - 0x10 = j, ebp - 0x14 = k 804909f: c7 45 f4 01 00 00 00 movl $0x1,-0xc(%ebp) 80490a6: c7 45 f0 02 00 00 00 movl $0x2,-0x10(%ebp) 80490ad: c7 45 ec 03 00 00 00 movl $0x3,-0x14(%ebp) # i → eax 80490b4: 8b 45 f4 mov -0xc(%ebp),%eax # -0x3c(%ebp, %eax, 4) = ebp - 0x3c + eax * 4(この4がintのポインタ演算時にでてくる4) = a[i] ← 0xff 80490b7: c7 44 85 c4 ff 00 00 movl $0xff,-0x3c(%ebp,%eax,4) 80490be: 00 # i → edx 80490bf: 8b 55 f4 mov -0xc(%ebp),%edx # i * 10 → eax 80490c2: 89 d0 mov %edx,%eax 80490c4: c1 e0 02 shl $0x2,%eax 80490c7: 01 d0 add %edx,%eax 80490c9: 01 c0 add %eax,%eax # j → edx 80490cb: 8b 55 f0 mov -0x10(%ebp),%edx # i * 10 + j → eax 80490ce: 01 d0 add %edx,%eax # ebp - 0x1cc + eax * 4 = bの先頭アドレス + (i * 10 + j) * 4 = b[i][j] ← 0xff 80490d0: c7 84 85 34 fe ff ff movl $0xff,-0x1cc(%ebp,%eax,4) 80490d7: ff 00 00 00 # j → edx, i → ecx 80490db: 8b 55 f0 mov -0x10(%ebp),%edx 80490de: 8b 4d f4 mov -0xc(%ebp),%ecx # j * 10 → eax 80490e1: 89 d0 mov %edx,%eax 80490e3: c1 e0 02 shl $0x2,%eax 80490e6: 01 d0 add %edx,%eax 80490e8: 01 c0 add %eax,%eax # i * 100 → edx 80490ea: 6b d1 64 imul $0x64,%ecx,%edx # i * 100 + j * 100 → edx 80490ed: 01 c2 add %eax,%edx # k → eax 80490ef: 8b 45 ec mov -0x14(%ebp),%eax # i * 100 + j * 10 + k → eax 80490f2: 01 d0 add %edx,%eax # ebp - 0x116c + eax * 4 = c[i][j][k] ← 0xff 80490f4: c7 84 85 94 ee ff ff movl $0xff,-0x116c(%ebp,%eax,4) 80490fb: ff 00 00 00 # プログラムはまだまだ続く...