Linux 86版5.3節Cスタイルデータ構造メモリレイアウトの配列


C言語では配列は同じタイプの変数の集合体である.この定義により、配列の特徴が大まかにわかります.
1.最初の要素があります.ヘッダ要素のアドレスは配列アドレスと同様にベースアドレスである
2.各要素のサイズは同じです.では、各要素のベースアドレスに対するオフセット値は、要素サイズとインデックス値の積であるべきである.
すなわち,ベースアドレスとインデックス値に比例するオフセット値が配列の特徴である可能性がある.
やはり上記のように各タイプの配列を1つずつ探究する.
まずchar型の配列を見てみましょう
 #include <stdio.h>
 int main()
 {
     char buf[16];
     char c = 'a';
 
     printf( "head of array:%x, tail of array:%x", buf, &buf[15] );
 
     for ( int i = 0; i < 16; i++, c++ )
     {
         buf[i] = c;
     }
     buf[15] = '\0';

     printf( "%s
", buf ); return 0; }

アセンブリをもう一度見てみましょう.
(gdb) disassemble main
Dump of assembler code for function main:
   0x080485a0 <+0>:     push   %ebp
   0x080485a1 <+1>:     mov    %esp,%ebp
   0x080485a3 <+3>:     and    $0xfffffff0,%esp
   0x080485a6 <+6>:     sub    $0x30,%esp
   0x080485a9 <+9>:     movb   $0x61,0x2f(%esp)

   0x080485ae <+14>:    lea    0x18(%esp),%eax
   0x080485b2 <+18>:    add    $0xf,%eax
   0x080485b5 <+21>:    mov    %eax,0x8(%esp)
   0x080485b9 <+25>:    lea    0x18(%esp),%eax
   0x080485bd <+29>:    mov    %eax,0x4(%esp)
   0x080485c1 <+33>:    movl   $0x80486b4,(%esp)
   0x080485c8 <+40>:    call   0x8048460 <printf@plt>

   0x080485cd <+45>:    movl   $0x0,0x28(%esp)
   0x080485d5 <+53>:    jmp    0x80485f2 <main+82>

   0x080485d7 <+55>:    lea    0x18(%esp),%edx
   0x080485db <+59>:    mov    0x28(%esp),%eax
   0x080485df <+63>:    add    %eax,%edx
   0x080485e1 <+65>:    movzbl 0x2f(%esp),%eax
   0x080485e6 <+70>:    mov    %al,(%edx)
   0x080485e8 <+72>:    addl   $0x1,0x28(%esp)
   0x080485ed <+77>:    addb   $0x1,0x2f(%esp)
   0x080485f2 <+82>:    cmpl   $0xf,0x28(%esp)
   0x080485f7 <+87>:    setle  %al
   0x080485fa <+90>:    test   %al,%al
   0x080485fc <+92>:    jne    0x80485d7 <main+55>

   0x080485fe <+94>:    movb   $0x0,0x27(%esp)
   0x08048603 <+99>:    lea    0x18(%esp),%eax
   0x08048607 <+103>:   mov    %eax,(%esp)
   0x0804860a <+106>:   call   0x8048470 <puts@plt>
   0x0804860f <+111>:   mov    $0x0,%eax
   0x08048614 <+116>:   jmp    0x804861e <main+126>
   0x08048616 <+118>:   mov    %eax,(%esp)
   0x08048619 <+121>:   call   0x8048490 <_Unwind_Resume@plt>
   0x0804861e <+126>:   leave  
   0x0804861f <+127>:   ret    
End of assembler dump.

から
   0x080485ae <+14>:    lea    0x18(%esp),%eax
   0x080485b2 <+18>:    add    $0xf,%eax
   0x080485b5 <+21>:    mov    %eax,0x8(%esp)
   0x080485b9 <+25>:    lea    0x18(%esp),%eax
   0x080485bd <+29>:    mov    %eax,0x4(%esp)
   0x080485c1 <+33>:    movl   $0x80486b4,(%esp)
   0x080485c8 <+40>:    call   0x8048460 <printf@plt>

次のように表示されます.
1.第2のパラメータ、bufはeaxから得られ、eaxはesp+0 x 18、すなわちesp+0 x 18はbufのベースアドレスである
2.第3のパラメータ,&buf[15]は、esp+0 x 18+0 xfから得られる.0 xfはbuf[15]のbufベースアドレスに対するオフセットとちょうど等しい.
3.buf[15]のアドレスがbuf[0]よりも高いことから、配列はスタック上で増加していることがわかる.
 
から
   0x080485a9 <+9>:     movb   $0x61,0x2f(%esp)

局所変数cはesp+0 x 2 fに置かれていることがわかる.
 
から
   0x080485d7 <+55>:    lea    0x18(%esp),%edx
   0x080485db <+59>:    mov    0x28(%esp),%eax
   0x080485df <+63>:    add    %eax,%edx
   0x080485e1 <+65>:    movzbl 0x2f(%esp),%eax
   0x080485e6 <+70>:    mov    %al,(%edx)
   0x080485e8 <+72>:    addl   $0x1,0x28(%esp)
   0x080485ed <+77>:    addb   $0x1,0x2f(%esp)
   0x080485f2 <+82>:    cmpl   $0xf,0x28(%esp)
   0x080485f7 <+87>:    setle  %al
   0x080485fa <+90>:    test   %al,%al
   0x080485fc <+92>:    jne    0x80485d7 <main+55>

このサイクルの中の
   0x080485ed <+77>:    addb   $0x1,0x2f(%esp)

わかる
cインクリメントのステップ長は1で、ちょうどcharの大きさと同じです.
そして
   0x080485d7 <+55>:    lea    0x18(%esp),%edx
   0x080485db <+59>:    mov    0x28(%esp),%eax
   0x080485df <+63>:    add    %eax,%edx

および
   0x080485e8 <+72>:    addl   $0x1,0x28(%esp)

配列の要素アドレスは確かにインクリメントされ、各要素のアドレスはesp+0 x 18+i、すなわちベースアドレス+iであることがわかる.
 
 
PS:以下のアセンブル指令
   0x080485e8 <+72>:    addl   $0x1,0x28(%esp)
   0x080485ed <+77>:    addb   $0x1,0x2f(%esp)
   0x080485f2 <+82>:    cmpl   $0xf,0x28(%esp)

という意味で、c++、i++とi<15です.しかし,c++をi++に入れ,i<15間は主にc,iの2つの変数操作の命令間に依存せず,ミキシングすればマルチコアマルチスレッドプロセッサで同時に実行できる.
断点を打って上記の結論を検証します
(gdb) tbreak *0x080485fe
Temporary breakpoint 1 at 0x80485fe
(gdb) r
Starting program: /home/buckxu/work/5/2/xuzhina_dump_c5_s2 

Temporary breakpoint 1, 0x080485fe in main ()
 (gdb) x /16c $esp+0x18
0xbffff468:     97 'a'  98 'b'  99 'c'  100 'd' 101 'e' 102 'f' 103 'g' 104 'h'
0xbffff470:     105 'i' 106 'j' 107 'k' 108 'l' 109 'm' 110 'n' 111 'o' 112 'p'

shortの配列を見続けます.
   #include <stdio.h>
   int main()
   {
       short buf[16];
       short s = 'a';
  
       printf( "head of array:%x, tail of array:%x", buf, &buf[15] );
   
       for ( int i = 0; i < 16; i++, s++ )
      {
          buf[i] = s;
      }
  
  
      return buf[15];
  }

対応するアセンブリを見てみましょう.
(gdb) disassemble main
Dump of assembler code for function main:
   0x08048570 <+0>:     push   %ebp
   0x08048571 <+1>:     mov    %esp,%ebp
   0x08048573 <+3>:     and    $0xfffffff0,%esp
   0x08048576 <+6>:     sub    $0x40,%esp
   0x08048579 <+9>:     movw   $0x61,0x3e(%esp)

   0x08048580 <+16>:    lea    0x18(%esp),%eax
   0x08048584 <+20>:    add    $0x1e,%eax
   0x08048587 <+23>:    mov    %eax,0x8(%esp)
   0x0804858b <+27>:    lea    0x18(%esp),%eax
   0x0804858f <+31>:    mov    %eax,0x4(%esp)
   0x08048593 <+35>:    movl   $0x8048674,(%esp)
   0x0804859a <+42>:    call   0x8048440 <printf@plt>

   0x0804859f <+47>:    movl   $0x0,0x38(%esp)
   0x080485a7 <+55>:    jmp    0x80485c2 <main+82>

   0x080485a9 <+57>:    mov    0x38(%esp),%eax
   0x080485ad <+61>:    movzwl 0x3e(%esp),%edx
   0x080485b2 <+66>:    mov    %dx,0x18(%esp,%eax,2)
   0x080485b7 <+71>:    addl   $0x1,0x38(%esp)
   0x080485bc <+76>:    addw   $0x1,0x3e(%esp)
   0x080485c2 <+82>:    cmpl   $0xf,0x38(%esp)
   0x080485c7 <+87>:    setle  %al
   0x080485ca <+90>:    test   %al,%al
   0x080485cc <+92>:    jne    0x80485a9 <main+57>

   0x080485ce <+94>:    movzwl 0x36(%esp),%eax
   0x080485d3 <+99>:    cwtl   
   0x080485d4 <+100>:   jmp    0x80485de <main+110>
   0x080485d6 <+102>:   mov    %eax,(%esp)
   0x080485d9 <+105>:   call   0x8048460 <_Unwind_Resume@plt>
   0x080485de <+110>:   leave  
   0x080485df <+111>:   ret    
End of assembler dump.

char型配列の類似分析によれば、以下のことが得られる.
1.局所変数sはesp+0 x 3 eに格納
2.bufの先頭アドレスはesp+0 x 18であり、末尾要素のアドレスはesp+0 x 18+0 x 1 eである.bufはスタックの中でも増加していることがわかります.
3.bufの空間サイズは0 x 1 e+2=0 x 20=32である.ちょうど16個のショットタイプの大きさです.
4.0 x 080485 bcから分かるように、short型配列のインクリメントステップ長は2であり、ちょうどshortの大きさである.
5.0 x 080485 b 2から分かるように、short配列の各要素への参照は、esp+0 x 18+2*eax、すなわちベースアドレス+i*sizeof(short)を用いる
 
int,long,float,doubleに続けて、次の表を得ることができます.
を選択します.
とくせい
char
ベースアドレス+インデックス値*1
short
ベースアドレス+インデックス値*2
int
ベースアドレス+インデックス値*4
long
32-bit:ベースアドレス+インデックス値*4 64-bit:ベースアドレス+インデックス値*8
float
ベースアドレス+インデックス値*4(単精度は4バイトですので)は、浮動小数点計算の指令に合わせて確認します
double
ベースアドレス+インデックス値*8(ダブル精度8バイト)浮動小数点計算の指令に合わせて確認する
ししん
32-bit:ベースアドレス+インデックス値*4 64-bit:ベースアドレス+インデックス値*8
実は、ベースアドレス+インデックス値*sizeof(element)など、アセンブリで多くの表現ができます.次のように
lea$base,%eax//baseアドレスをeaxにmov$index,%ecx//indexをecxに入れます.mul$4,%ecx add%ecx,%eax//ここでeaxはindexが指す要素アドレスを格納します.
詳しくは「レジスタアドレス方式」の「レジスタアドレス方式」を検索できます.