版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344512
在C语言里,数组就是相同类型变量的集合体。由这个定义,可大致得知数组的特征:
1.有首元素。而首元素的地址和数组地址一样,即有基地址
2.每个元素的大小是一样的。那么每个元素相对基地址的偏移值应该是元素大小和索引值的乘积。
也就是说,基地址和与索引值成比例的偏移值有可能是数组的特征。
还是按照上面的方式来逐个对各类型的数组进行探究。
先看一下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\n", 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. 第二个参数,buf是由eax得来,而eax是由esp+0x18,也就是说,esp+0x18是buf的基地址
2. 第三个参数,&buf15,是由esp+0x18 + 0xf得来的。0xf刚好和buf15相对buf基地址的偏移相等。
3. 由buf15的地址比buf0的高,可知,数组在栈上是递增的。
由
0x080485a9 <+9>: movb $0x61,0x2f(%esp)
可知,局部变量c是放在esp+0x2f里。
再由
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+0x18+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两个变量操作的指令之间没有依赖,混编的话,在多核多线程处理器能够同时并发执行。
打一下断点来验证一下上面结论
(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+0x3e
2. buf的首地址是esp+0x18,尾元素的地址是esp+0x18+0x1e。可见buf在栈里也是递增的。
3. buf的空间大小是0x1e+2=0x20 = 32。正好是16个short类型的大小。
4. 由0x080485bc可知,short型数组的递增步长是2,刚好是short的大小。
5. 由0x080485b2可知,对short数组每一个元素的引用,都要用到esp+0x18+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指向的元素地址了。 |
|:----|
详细可以搜索一下“寄存器寻址方式”里的”寄存器变址寻址方式“