zoukankan      html  css  js  c++  java
  • 深入讲解函数中分配内存问题

    先看这样的代码

    1void MyNew(int *p)
    2{
    3     p = new int;
    4}
    5
    6int main()
    7{
    8    int *= NULL;
    9     MyNew(p);
    10}


    开始写了一篇:函数中分配内存的问题(点击进入),通过说明他们产生了拷贝,而导致p不能成功分配。但并未提出事实根据,下面我们来仔细看看具体原因。

    我们需要弄清两点:1、main函数中的p与MyNew函数中的p是不是一样;2、如果不一样,是怎么导致了不一样的。

    第一点很好看,我们可以在编译器(VC环境,我用的是VS 2005)的监视窗口中跟踪p的地址。 
             在监视窗口中增加一个对 &p的监视,然后我们在int*p = NULL处添加一个断点。单步执行,停在MyNew函数前,此时我们可以看到,&p的值为 0x0012ff60 .   然后,我们单步进入MyNew函数,此时我们可以发现,&p的值变成了0x0012fe8c   明显,它们不是同一个东西,这样在MyNew操作的时候,操作的就不是我们想要操作的那个p(0x0012ff60). 好了,不要去猜测这两个数字之间的关系,接下来会给你一个满意的答案.

    先看看下面这个反汇编的结果

    int *p = NULL;
    0041153E mov         dword ptr [p],0 

    MyNew(p);
    00411545 mov         eax,dword ptr [p] 
    00411548 push        eax 

    00411549 call        MyNew (41116Dh) 

    红色部分就是将p作为参数压栈,然后call MyNew,注意,此时我们的p已经被保存起来了。


    void MyNew(int *p)
    {
    004114C0 push        ebp 
    004114C1 mov         ebp,esp 
    004114C3 sub         esp,0CCh   //红色:分配33*4Bytes 临时空间 
    004114C9 push        ebx 
    004114CA push        esi 
    004114CB push        edi 
    004114CC lea         edi,[ebp-0CCh] 
    004114D2 mov         ecx,33h 
    004114D7 mov         eax,0CCCCCCCCh 
    004114DC rep stos    dword ptr es:[edi]    //蓝色:初始化分配的空间为 0xcccccccc

    p = new int;
    004114DE push        4    
    004114E0 call        operator new (411190h)   //调用new 返回值存放于eax中。
    004114E5 add         esp,4 

    004114E8 mov         dword ptr [ebp-0C8h],eax //将new出来的地址放到ebp-0c8h中
    004114EE mov         eax,dword ptr [ebp-0C8h] //将new出来的值放到eax中,作为返回值。
    004114F4 mov         dword ptr [p],eax //将eax中的值放入p中
    }

    //下面是清栈操作
    004114F7 pop         edi 
    004114F8 pop         esi 
    004114F9 pop         ebx 
    004114FA add         esp,0CCh //清除临时变量
    00411500 cmp         ebp,esp 
    00411502 call        @ILT+325(__RTC_CheckEsp) (41114Ah) 
    00411507 mov         esp,ebp 
    00411509 pop         ebp 
    0041150A ret     


    上面的东西不能说明根本问题,因为没有作任何分析,下面我们就来仔细分析一下,特别是最后的 004114F4 mov         dword ptr [p],eax   有人就会问,既然已经放回了p中,为啥p还是没变呢。


    这就是new之间的堆栈空间示意图,可以看出,我们传入的参数是ebp+8,而当new回来后,却用的是004114E8 mov         dword ptr [ebp-0C8h],eax 很明显,ebp-0c8 是临时分配的空间。 而放入的那个p, 的确,它是放了,但是,这个p其实不是main中的那个p了。所以,最后,main函数中,p所指向的地址并没有改变。



    void MyNew(int *&p)
    {
         p 
    = new int;
    }


    我们将代码稍作修改,改成传递指针的引用,那又会发生什么呢。首先,我们按照上面的方法检测其地址。 你会发现,两个函数中的地址都是 0x0012ff60.
    那,为什么会这样呢,我们看看两个地方,第一就是参数传递时的压栈。
    00411535 lea         eax,[p] 
    00411538 push        eax
     
    00411539 call        MyNew (4111F4h) 

    可以看出,这次传递的,并非是像开始一样 mov eax, dword ptr[p]   。二者的差别在于,上一次(没有采用引用传递)传递的是值,而这一次(采用了引用传递)传递的是指针p的地址。

    接下来,我们再来看看刚刚new出来之后赋值的地方。

    004114E8 mov         dword ptr [ebp-0C8h],eax 
    004114EE mov         eax,dword ptr [p] 
    004114F1 mov         ecx,dword ptr [ebp-0C8h] 
    004114F7 mov         dword ptr [eax],ecx
     


    可以发现,这正是我们传中说的:取得p的地址,采用*p求出p所指向的地址。然后对*p赋值,以改变它的值。。

    还有一种就是指针的指针void MyNew(int** p){*p = new int}的方式,其实这个传递引用是完全等效的。甚至,反汇编后,他们是同样的代码。

  • 相关阅读:
    iOS:后台定位并实时向服务器发送位置
    iOS:创建Siri 功能
    Cocoa编程开发者手册
    iOS应用开发最佳实践
    Linux Shell编程与编辑器使用详解
    从虚拟化到云计算
    软件集成策略——如何有效率地提升质量
    水色物语:清新水彩手绘插画技法
    易用为王:改进产品设计的10个策略
    iOS Web应用开发:运用HTML5、CSS3与JavaScript
  • 原文地址:https://www.cnblogs.com/10jschen/p/2595860.html
Copyright © 2011-2022 走看看