【反汇编分析】下标寻址和指针寻址

来源:互联网 发布:mac系统安装 编辑:程序博客网 时间:2024/06/10 03:22

      本节研究数组下标寻址和指针寻址的相关问题;


基本知识

数组

(1)数组名的值是一个指针常量;如int a[10],a为一个指向int的指针常量;注意不能修改常量的值;请注意a和&a虽然地址值是一样的,但是寻址的地址空间是不一样的;比如a+1是指向数组第2个变量,而&a+1是越过整个数组的指向;具体如下:

  int a[5];  int *b = a;  int (*c)[5] = &a;    int d[4][5];  int (*e)[5] = d;

说明几点:

(1.1)b指向a的首元素;c指向数组a的整个地址空间(c+1将会跳过整个数组a的地址空间),e指向d[0]的地址空间,因此e+1将会指向d[1]的地址空间;

示意图如下:




指针

(1)指针其实就是存放的一个地址;而指向指针的指针也就是存放一个地址的地址;如int *b=a;b中存放的就是变量a的地址,而int** c=&b;c中存放的是变量b的地址;

代码片段如下:

  int a = 1;  int *b = &a;  int **c = &b;    cout << &a << endl;  cout << b << endl;  cout << c << endl;
输出如下:

0xbf9449f80xbf9449f80xbf9449f4

示意图如下:



交换两个变量

(1)使用中间内存temp,temp通常是寄存器(那么交换两个变量,涉及到两次内存读和两次内存写),但是也有可能是栈上内存(那么交换两个变量,涉及到3次内存读和3次内存写),但是栈已经开在那了,不用也浪费;

(2)不使用中间内存;针对指针寻址,如果使用异或的方法,每一次异或操作,都会涉及到两次读和一次写(编译器不优化),如*begin ^= *end,因此总共就有6次内存读和3次内存写,3次异或编译器优化的话,也需要两次内存读,3次内存写和3次异或操作


数组寻址和指针寻址

栈中字符串

代码片段如下

int main(){  char a[] = "hello";  char* b = a;  printf("%c\n", a[0]);  printf("%c\n", *b);  return 0;}

反汇编部分代码如下

 80485e6:55                   push   %ebp 80485e7:89 e5                mov    %esp,%ebp 80485e9:51                   push   %ecx 80485ea:83 ec 14             sub    $0x14,%esp 80485ed:c7 45 ee 68 65 6c 6c movl   $0x6c6c6568,-0x12(%ebp)  #存放lleh(68656c6c)到ebp指定的地址ebp-0x12 80485f4:66 c7 45 f2 6f 00    movw   $0x6f,-0xe(%ebp)         #存放o(6f)到ebp指定的地址ebp-0xe 80485fa:8d 45 ee             lea    -0x12(%ebp),%eax         #取地址到eax 80485fd:89 45 f4             mov    %eax,-0xc(%ebp)          #存放eax到ebp-oxc,其实就是b 8048600:0f b6 45 ee          movzbl -0x12(%ebp),%eax         #直接从数组中获得a[0] 8048604:0f be c0             movsbl %al,%eax 8048607:83 ec 08             sub    $0x8,%esp 804860a:50                   push   %eax 804860b:68 68 87 04 08       push   $0x8048768 8048610:e8 83 fe ff ff       call   8048498 <printf@plt> 8048615:83 c4 10             add    $0x10,%esp 8048618:8b 45 f4             mov    -0xc(%ebp),%eax          #取地址b到eax,一次寻址,获得b 804861b:0f b6 00             movzbl (%eax),%eax              #取eax地址的内容到eax(零扩展),二次寻址,获得*b 804861e:0f be c0             movsbl %al,%eax                 #取eax内容的低16位到eax(符号扩展) 8048621:83 ec 08             sub    $0x8,%esp 8048624:50                   push   %eax                  8048625:68 68 87 04 08       push   $0x8048768 804862a:e8 69 fe ff ff       call   8048498 <printf@plt>

说明几点:

(1)由反汇编可以得出,数组寻址只需要经过一次寻址就可以获得a[0]的内容,通过movzbl -0x12(%ebp),%eax来获得a[0]的;而指针寻址需要两次寻址,mov    -0xc(%ebp),%eax首先获得b本身的值,然后通过movzbl (%eax),%eax 获得*b的内容;


常量区中字符串

代码片段如下

int main(){  char* a = "hello";  char* b = a;  printf("%c\n", a[0]);  printf("%c\n", *b);  return 0;}
反汇编分析如下

 80485ed:c7 45 f4 68 87 04 08 movl   $0x8048768,-0xc(%ebp)    #将"hello"字符串的地址放入到ebp-0xc(其实也就是a) 80485f4:8b 45 f4             mov    -0xc(%ebp),%eax          #将地址a赋值给eax 80485f7:89 45 f0             mov    %eax,-0x10(%ebp)         #将eax赋值给b 80485fa:8b 45 f4             mov    -0xc(%ebp),%eax          #取地址a 80485fd:0f b6 00             movzbl (%eax),%eax              #取a的内容a[0] 8048600:0f be c0             movsbl %al,%eax 8048603:83 ec 08             sub    $0x8,%esp 8048606:50                   push   %eax 8048607:68 6e 87 04 08       push   $0x804876e 804860c:e8 87 fe ff ff       call   8048498 <printf@plt> 8048611:83 c4 10             add    $0x10,%esp 8048614:8b 45 f0             mov    -0x10(%ebp),%eax         #取地址b 8048617:0f b6 00             movzbl (%eax),%eax              #取b的内容*b 804861a:0f be c0             movsbl %al,%eax 804861d:83 ec 08             sub    $0x8,%esp 8048620:50                   push   %eax 8048621:68 6e 87 04 08       push   $0x804876e 8048626:e8 6d fe ff ff       call   8048498 <printf@plt>
说明几点:

(1)经过反汇编分析对于常量区中的字符串,a[0]和*b都是一样的,都要经过两次寻址来获得内容,因为此时a和b都是保存的地址;


全局区字符串

代码片段如下

char a[] = "hello";int main(){  char* b = a;  printf("%c\n", a[0]);  printf("%c\n", *b);  return 0;}
反汇编部分代码如下

 80485ea:83 ec 14             sub    $0x14,%esp 80485ed:c7 45 f4 40 99 04 08 movl   $0x8049940,-0xc(%ebp)  #存入到b 80485f4:0f b6 05 40 99 04 08 movzbl 0x8049940,%eax     #直接获得a[0]的内容 80485fb:0f be c0             movsbl %al,%eax 80485fe:83 ec 08             sub    $0x8,%esp 8048601:50                   push   %eax 8048602:68 68 87 04 08       push   $0x8048768 8048607:e8 8c fe ff ff       call   8048498 <printf@plt> 804860c:83 c4 10             add    $0x10,%esp 804860f:8b 45 f4             mov    -0xc(%ebp),%eax    #首先获得地址b 8048612:0f b6 00             movzbl (%eax),%eax        #获得b的内容 8048615:0f be c0             movsbl %al,%eax 8048618:83 ec 08             sub    $0x8,%esp 804861b:50                   push   %eax 804861c:68 68 87 04 08       push   $0x8048768 8048621:e8 72 fe ff ff       call   8048498 <printf@plt>
说明几点

(1)对于全局区字符串,a[0]只需要通过一次寻址就可以获得,而*b需要通过两次寻址获得;


全局区中常量字符串

代码片段如下

char* a = "hello";int main(){  char* b = a;  printf("%c\n", a[0]);  printf("%c\n", *b);  return 0;}
反汇编部分代码如下
 80485ea:83 ec 14             sub    $0x14,%esp 80485ed:a1 48 99 04 08       mov    0x8049948,%eax 80485f2:89 45 f4             mov    %eax,-0xc(%ebp) 80485f5:a1 48 99 04 08       mov    0x8049948,%eax   #首先获得a 80485fa:0f b6 00             movzbl (%eax),%eax      #获得a里的内容 80485fd:0f be c0             movsbl %al,%eax 8048600:83 ec 08             sub    $0x8,%esp 8048603:50                   push   %eax 8048604:68 6e 87 04 08       push   $0x804876e 8048609:e8 8a fe ff ff       call   8048498 <printf@plt> 804860e:83 c4 10             add    $0x10,%esp 8048611:8b 45 f4             mov    -0xc(%ebp),%eax   #获得b 8048614:0f b6 00             movzbl (%eax),%eax       #获得b中的内容 8048617:0f be c0             movsbl %al,%eax 804861a:83 ec 08             sub    $0x8,%esp 804861d:50                   push   %eax 804861e:68 6e 87 04 08       push   $0x804876e 8048623:e8 70 fe ff ff       call   8048498 <printf@plt>

说明几点

(1)对于全局区的常量字符串,a[0]和*b都是一样的,都要经过两次寻址来获得内容;

(2)对于堆区申请的字符串,使用a来指向该分配的内存,a[0]和*b都是一样的,都要经过两次寻址来获得内容,因为此时a和b都是保存的地址;


循环赋值

下标方式

代码片段如下

int a[5];int b[5];int main(){  for(int i=0; i < 5; ++i)    a[i] = b[i];      return 0;}

反汇编部分代码如下

080485ac <main>: 80485ac:55                   push   %ebp 80485ad:89 e5                mov    %esp,%ebp 80485af:83 ec 10             sub    $0x10,%esp 80485b2:c7 45 fc 00 00 00 00 movl   $0x0,-0x4(%ebp)         #i赋值为0; 80485b9:eb 18                jmp    80485d3 <main+0x27>     #先去判断条件 80485bb:8b 45 fc             mov    -0x4(%ebp),%eax         #获得i 80485be:8b 14 85 e8 98 04 08 mov    0x80498e8(,%eax,4),%edx #直接获得b[i],这边是有一个乘法和加法 80485c5:8b 45 fc             mov    -0x4(%ebp),%eax         #获得i 80485c8:89 14 85 d4 98 04 08 mov    %edx,0x80498d4(,%eax,4) #将b[i]赋值给a[i],这边是有一个乘法和加法 80485cf:83 45 fc 01          addl   $0x1,-0x4(%ebp)         #i加1; 80485d3:83 7d fc 04          cmpl   $0x4,-0x4(%ebp) 80485d7:7e e2                jle    80485bb <main+0xf>      #继续执行 80485d9:b8 00 00 00 00       mov    $0x0,%eax 80485de:c9                   leave   80485df:c3                   ret    


指针方式

代码片段如下

int a[5];int b[5];int main(){  int* p1 = a;  int* p2 = b;  for(int i = 0; i < 5; ++i)    *p1++ = *p2++;      return 0;}
反汇编部分代码如下
 80485b2:c7 45 fc e4 98 04 08 movl   $0x80498e4,-0x4(%ebp)    #数组a的首地址存入到a中 80485b9:c7 45 f8 f8 98 04 08 movl   $0x80498f8,-0x8(%ebp)    #数组b的首地址存入到b中 80485c0:c7 45 f4 00 00 00 00 movl   $0x0,-0xc(%ebp)          #i值为0 80485c7:eb 1a                jmp    80485e3 <main+0x37>      #先去判断条件,到e3处 80485c9:8b 45 fc             mov    -0x4(%ebp),%eax    #取地址a 80485cc:8d 50 04             lea    0x4(%eax),%edx     #取地址a+4放入到edx 80485cf:89 55 fc             mov    %edx,-0x4(%ebp)    #将a+4放入到原来的局部变量a中,而原先的地址由eax保存  80485d2:8b 55 f8             mov    -0x8(%ebp),%edx    #取地址b 80485d5:8d 4a 04             lea    0x4(%edx),%ecx     #取地址b+4放入到ecx 80485d8:89 4d f8             mov    %ecx,-0x8(%ebp)    #将b+4放入到原来的局部变量b中,而原先的地址由edx保存  80485db:8b 12                mov    (%edx),%edx        #取b的内容 80485dd:89 10                mov    %edx,(%eax)        #将b的内容放入到eax对应的地址中 80485df:83 45 f4 01          addl   $0x1,-0xc(%ebp)    #i加1 80485e3:83 7d f4 04          cmpl   $0x4,-0xc(%ebp) 80485e7:7e e0                jle    80485c9 <main+0x1d>
说明几点:

(1)下标方式和指针方式寻址的反汇编可以得出,下标方式直接通过mov    0x80498e8(,%eax,4),%edx来获得内容,但这其实是包括一个乘法和加法后的寻址的;而对于指针寻址,需要两次寻址,首先获得本身的地址,然后还要获得地址的内容;

(2)对于栈中的数组,指针寻址和全局区的数组寻址类似;对于下标寻址,有原来的直接地址变为了帧指针,即变为了类似mov    -0x2c(%ebp,%eax,4),%edx;


0 0
原创粉丝点击