zoukankan      html  css  js  c++  java
  • 【反汇编玩耍2】论指针和数组

    在反汇编观察后,你会发现:指针变量就是块内存区域,里面存放的是地址,你可以通过这个地址访问其它内存。

                                                   数组就是块连续的内存区域,里面连续排列着同样size的内存,多维数组也是一样的。

    上述很简单,就不贴代码赘述了。

    但人们一般纠结这样一个问题:数组名 是不是 一种指针?

    也就是说arr[]的这个arr是不是一种指针?

    这个问题之前csdn论坛上讨论的热火朝天:

    http://bbs.csdn.net/topics/380226723

    http://blog.csdn.net/yby4769250/article/details/7294718#reply

    一种看法就是把这个数组名当作一种特殊的指针来看待,特殊之处在于是常量,不能改变其指向的位置。

    一种是透过C语言,从C语言编译之后的汇编,视角来看,认为数组名和指针在内存中完全不是一回事。

    太认真考虑其定义和概念就接近于一种“玄学”,我这里就单纯讲反汇编角度来分析 数组名 和 指针在内存中的差别吧。

    随便写的代码:

    #include <stdio.h>
    
    void func(int *p) {
        *(p+1) = 2;
    }
    
    void main() {
            int v = 1;
            int *p = &v;
            int v2 = *p + v;
            int arr[100];
            int v3 = arr[0] + v;
            int v4 = *(arr+2) + v2;
            func(arr);
    }
    void main() {
    008114D0  push        ebp  
    008114D1  mov         ebp,esp  
    008114D3  sub         esp,298h  
    008114D9  push        ebx  
    008114DA  push        esi  
    008114DB  push        edi  
    008114DC  lea         edi,[ebp-298h]  
    008114E2  mov         ecx,0A6h  
    008114E7  mov         eax,0CCCCCCCCh  
    008114EC  rep stos    dword ptr es:[edi]  
    008114EE  mov         eax,dword ptr ds:[00818000h]  
    008114F3  xor         eax,ebp  
    008114F5  mov         dword ptr [ebp-4],eax  
            int v = 1;
    008114F8  mov         dword ptr [v],1  
            int *p = &v;
    008114FF  lea         eax,[v]  
    00811502  mov         dword ptr [p],eax  
            int v2 = *p + v;
    00811505  mov         eax,dword ptr [p]  
    00811508  mov         ecx,dword ptr [eax]  
    0081150A  add         ecx,dword ptr [v]  
    0081150D  mov         dword ptr [v2],ecx  
            int arr[100];
            int v3 = arr[0] + v;
    00811510  mov         eax,4  
    00811515  imul        eax,eax,0  
    00811518  mov         ecx,dword ptr arr[eax]  
    0081151F  add         ecx,dword ptr [v]  
    00811522  mov         dword ptr [v3],ecx  
            int v4 = *(arr+2) + v2;
    00811528  mov         eax,dword ptr [ebp-1B4h]  
    0081152E  add         eax,dword ptr [v2]  
    00811531  mov         dword ptr [v4],eax  
            func(arr);
    00811537  lea         eax,[arr]  
    0081153D  push        eax  
    0081153E  call        func (0811226h)  
    00811543  add         esp,4  
    }

     可以看到arr[100]这个数组名arr在汇编代码中变成了一个单纯的标号,代表一个地址,这个地址是     数组这一列连续内存空间的首地址。

    假设数组地址是N,那么数组名在汇编代码中就是N本身。

    但对比来看,指针在这里是栈空间上申请的内存。这块内存里存着别的内存的地址。

    假设指针的地址是M,指针指向的内存的地址是N,那就是地址为M的内存里存着N。

    二者对比 数组名和指针的差别就清楚了吧。一个就只是标号(地址),一个是一块内存,内存里面存着地址。

    上述是讲数组名 和 指针的差别。

    下面要讲,

    而当数组作为函数参数时,其实就是转化为指针来玩的。

    先讲调用函数时的反汇编代码(这里取数组地址,然后压入栈再call func):

            func(arr);
    00811537  lea         eax,[arr]  
    0081153D  push        eax  
    0081153E  call        func (0811226h)  
    00811543  add         esp,4 

    再讲func函数里面对数组操作的反汇编代码:

    上面的代码的函数func:

    void func(int *p) {
        *(p+1) = 2;
    }

    反汇编代码为:

    void func(int *p) {
    00FA3DE0  push        ebp  
    00FA3DE1  mov         ebp,esp  
    00FA3DE3  sub         esp,0C0h  
    00FA3DE9  push        ebx  
    00FA3DEA  push        esi  
    00FA3DEB  push        edi  
    00FA3DEC  lea         edi,[ebp-0C0h]  
    00FA3DF2  mov         ecx,30h  
    00FA3DF7  mov         eax,0CCCCCCCCh  
    00FA3DFC  rep stos    dword ptr es:[edi]  
        *(p+1) = 2;
    00FA3DFE  mov         eax,dword ptr [p]  
    00FA3E01  mov         dword ptr [eax+4],2  
    }
    00FA3E08  pop         edi  
    00FA3E09  pop         esi  
    00FA3E0A  pop         ebx  
    00FA3E0B  mov         esp,ebp  
    00FA3E0D  pop         ebp  
    00FA3E0E  ret 

    这里形参为指针,没什么异议。

    改写一下,形参改为,arr[]的话:

    void func(int arr[]) {
        arr[1] = 2;
    }
    void func(int arr[]) {
    01173DE0  push        ebp  
    01173DE1  mov         ebp,esp  
    01173DE3  sub         esp,0C0h  
    01173DE9  push        ebx  
    01173DEA  push        esi  
    01173DEB  push        edi  
    01173DEC  lea         edi,[ebp-0C0h]  
    01173DF2  mov         ecx,30h  
    01173DF7  mov         eax,0CCCCCCCCh  
    01173DFC  rep stos    dword ptr es:[edi]  
        arr[1] = 2;
    01173DFE  mov         eax,4  
    01173E03  shl         eax,0  
    01173E06  mov         ecx,dword ptr [arr]  
    01173E09  mov         dword ptr [ecx+eax],2  
    }
    01173E10  pop         edi  
    01173E11  pop         esi  
    01173E12  pop         ebx  
    01173E13  mov         esp,ebp  
    01173E15  pop         ebp  
    01173E16  ret  

    可以看出二者(不管形参写成*p还是arr[])是没差别的。

    数组作为函数参数,都是会被转化为指针来操作。

    为何?

    因为调用函数前压入栈的是数组的地址,而不是整个数组。这个地址存在栈空间里,占有一定内存,所以成了一个指针!

    函数里面从栈里获取到这个指针,继续操作。

    这样是有一定道理的,因为数组可能很长,如果整个压入栈就是从新复制了一份空间,可能非常浪费,还不如指向前一个栈空间里数组的内存。

    最后我想说,我玩这个反汇编,似乎没啥实际用处,比如就算不懂数组名 和 指针差别,也可以写程序啊。

    但我总觉得,作为一个程序员,搞明白自己写的程序,到底是怎么被计算机运行的,会非常TMD爽。而不只是单纯的会写。

    所以我最近决定系统的玩一把反汇编。

    让自己对自己写的任何一行C、C++代码都对其运行原理、内存都一清二楚,看到C、C++代码就能迅速想象出汇编代码的样子。

    下一个课题:彻底搞明白,各种数据类型在内存中的存储,又要涉及补码了。hhh

  • 相关阅读:
    Day22:异常处理、socke基于TCP协议编程
    Day21:面向对象的软件开发、反射、对象的内置方法
    Day20:绑定方法与非绑定办法、多态和多态性
    Day19:继承实现的原理、子类中调用父类的方法、封装
    Day18:类的抽象、类的组合应用
    Day17:类的继承、派生、组合和接口
    Day16:面向对象编程——类和对象
    数据结构
    python爬虫篇之 性能相关
    scrapy-redis
  • 原文地址:https://www.cnblogs.com/rixiang/p/7121838.html
Copyright © 2011-2022 走看看