FromNandの日記

自分的備忘録

【C言語・アセンブリ】多次元配列のアクセスをアセンブラで見てみる

次のプログラムをアセンブルするといい。

配列の各次元の要素数を変えてみると、プログラムが興味深く変化する。

#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 


 # プログラムはまだまだ続く...