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中  这也是为什么 int* MyNew() {return new int;}能成功的原因
    }

    //下面是清栈操作
    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,已经不是那个p了,这个p指向的是我们栈空间里的临时变量,当函数返回后,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}的方式,其实这个传递引用是完全等效的。甚至,反汇编后,他们是同样的代码。

    结论:从上面的代码中我们可以看到,如果想要在函数内改变参数的值,则只能通过传递他的地址(引用也是传地址)方式,然后对其地址指向的内容进行变更!!!



    终于写完了。有很多地方觉得还是没讲清楚,希望各位大大指教,小弟立马修改。 洗过头,上班去!!

  • 相关阅读:
    工作中遇到的java 内存溢出,问题排查
    java线上内存溢出问题排查步骤
    性能测试-java内存溢出问题排查
    164 01 Android 零基础入门 03 Java常用工具类01 Java异常 04 使用try…catch…finally实现异常处理 04 终止finally执行的方法
    163 01 Android 零基础入门 03 Java常用工具类01 Java异常 04 使用try…catch…finally实现异常处理 03 使用多重catch结构处理异常
    162 01 Android 零基础入门 03 Java常用工具类01 Java异常 04 使用try…catch…finally实现异常处理 02 使用try-catch结构处理异常
    161 01 Android 零基础入门 03 Java常用工具类01 Java异常 04 使用try…catch…finally实现异常处理 01 try-catch-finally简介
    160 01 Android 零基础入门 03 Java常用工具类01 Java异常 03 异常处理简介 01 异常处理分类
    159 01 Android 零基础入门 03 Java常用工具类01 Java异常 02 异常概述 02 异常分类
    158 01 Android 零基础入门 03 Java常用工具类01 Java异常 02 异常概述 01 什么是异常?
  • 原文地址:https://www.cnblogs.com/qilinzi/p/1940496.html
Copyright © 2011-2022 走看看