Linux 86版5.3節Cスタイルデータ構造メモリレイアウトの配列
8583 ワード
C言語では配列は同じタイプの変数の集合体である.この定義により、配列の特徴が大まかにわかります.
1.最初の要素があります.ヘッダ要素のアドレスは配列アドレスと同様にベースアドレスである
2.各要素のサイズは同じです.では、各要素のベースアドレスに対するオフセット値は、要素サイズとインデックス値の積であるべきである.
すなわち,ベースアドレスとインデックス値に比例するオフセット値が配列の特徴である可能性がある.
やはり上記のように各タイプの配列を1つずつ探究する.
まずchar型の配列を見てみましょう
アセンブリをもう一度見てみましょう.
から
次のように表示されます.
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]よりも高いことから、配列はスタック上で増加していることがわかる.
から
局所変数cはesp+0 x 2 fに置かれていることがわかる.
から
このサイクルの中の
わかる
cインクリメントのステップ長は1で、ちょうどcharの大きさと同じです.
そして
および
配列の要素アドレスは確かにインクリメントされ、各要素のアドレスはesp+0 x 18+i、すなわちベースアドレス+iであることがわかる.
PS:以下のアセンブル指令
という意味で、c++、i++とi<15です.しかし,c++をi++に入れ,i<15間は主にc,iの2つの変数操作の命令間に依存せず,ミキシングすればマルチコアマルチスレッドプロセッサで同時に実行できる.
断点を打って上記の結論を検証します
shortの配列を見続けます.
対応するアセンブリを見てみましょう.
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が指す要素アドレスを格納します.
詳しくは「レジスタアドレス方式」の「レジスタアドレス方式」を検索できます.
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が指す要素アドレスを格納します.
詳しくは「レジスタアドレス方式」の「レジスタアドレス方式」を検索できます.