《coredump问题原理探究》Linux x86版5.3节C风格数据结构内存布局之数组

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xuzhina/article/details/8675206

在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.      第三个参数,&buf[15],是由esp+0x18 + 0xf得来的。0xf刚好和buf[15]相对buf基地址的偏移相等。

3.      由buf[15]的地址比buf[0]的高,可知,数组在栈上是递增的。

   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指向的元素地址了。

详细可以搜索一下“寄存器寻址方式”里的”寄存器变址寻址方式“

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ShaoYL

Objective-C中的@property和@synthesize用法

38811
来自专栏Java帮帮-微信公众号-技术文章全总结

Java面试系列9

✎一、Java有没有goto? java中的保留字,现在没有在java中使用。 ✎二、必须要知道的运行时异常 ArithmeticException 是...

2744
来自专栏刘望舒

Java虚拟机(二)对象的创建与OOP-Klass模型

前言 在前一篇文章中我们学习了Java虚拟机的结构原理与运行时数据区域,那么我们大概知道了Java虚拟机的内存的概况,那么内存中的数据是如何创建和访问的呢?这篇...

24010
来自专栏技术与生活

设计模式-模板方法模式

这里的抽象类就是抽象模板类,定义一个或者多个抽象方法,迫使子类来完成。同时定义一个模板方法,该方法一般情况下是一个具体方法,给出模板方法的执行过程,里面调用抽象...

771
来自专栏章鱼的慢慢技术路

Go指南_指针接收者

1002
来自专栏Crossin的编程教室

【Python 第24课】 if的嵌套

和for循环一样,if也可以嵌套使用,即在一个if/elif/else的内部,再使用if。这有点类似于电路的串联。 if 条件1: if 条件2: ...

3036
来自专栏Java技术栈

深度历险:Redis 内存模型详解

Redis 是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说 Redis 是实现网站高并发不可或缺的一部分。

1682
来自专栏塔奇克马敲代码

第 17 章 标准库特殊设施

722
来自专栏专注数据中心高性能网络技术研发

HERD--GCC宏

减少跳转语句失效时CPU等待取指令时间,提高CPU效率 使用__builtin_expect(EXP,N) 意思是EXP==N的概率很大 一般封装为LIKELY...

2935
来自专栏北京马哥教育

35个Python黑魔法级别

从我开始学习python的时候,我就开始自己总结一个python小技巧的集合。后来当我什么时候在Stack Overflow或者在某个开源软件里看到一段很酷代码...

3447

扫码关注云+社区