内存的分配VS回收&构造函数VS析构函数

简介:

之前有一个问题一直困扰着我,就是一个变量出了作用域,我以为这个变量的内存就被回收了,其实不是这样的,昨天问了一个高手,才豁然开朗,自己在看相关代码的反汇编代码,才知道原来真是这样就。这个问题,我想简单的说一下内存的分配VS回收&构造函数VS析构函数之间的关系。

我的疑问:为什么p出了作用域,指向p的ptr还能读到p中arr的内容,难道p出了作用域,还没有析构?

下面的内容会解答这个疑问,先说说跟这篇文章有关的内容。

可能是因为平时习惯的原因,我们在实例化一个对象的时候,往往是一条语句实现两个功能:1分配内存;2调用构造函数

复制代码
class A
{
public:
    A()
    {
        i=0;
        j=0;
    }

    ~A(){}
    int i;
    int j;
};
 
A a1; 
A * a2=new A();
复制代码

这两中方式都是一步实现两个操作,分配内存和调用构造函数,如果A没写构造函数,即没有构造函数(编译器也不会自动生成),当然就不需要调用构造函数。

其实这两步是可以分开的,A a1;这句分开不了这两步,但A * a2=new A();是可以分开,同等的代码如下:

void* memory=operator new(sizeof(A));//分配内存

A* a2=new(memory) A();//在memory上调用A的构造函数

回收的时候,我们可以这样写:

delete a2;//这句等同下面两句

//a2->~A();

//operator delete(memory);

如果A没有析构函数,当然delete时也不会调用,原因请看我的博客:构造函数产生的点及原因

也就是说A* a=new A();delete a;这两条语句,执行了四个操作:

分配内存->调用构造函数->调用析构函数->回收内存;

更多关于这四步分开的代码:

而我今天要说的是,这四步是完全可以分开的。既然这四步是可以分开的,那么解答上面那个疑问就很简单了。

Char* ptr;

{

      Point p;

      ptr=p;

}

P出了作用域,为什么ptr还能读到他的内容,原因很简单:因为上面几行代码只执行了前面三步,最后一步回收内存,还没有执行。出了作用域,就会执行析构,没说要回收内存,栈的内存要在方法返回之前才回收,也就是说一个方法如果大量的分配内存是很容易爆栈,即是你让栈中的变量出了作用域也没用,请不要搞混了。栈内存在方法返回的时候才回收,这一点就是爆栈的最重要原因,为什么不是在变量出作用域的时候,调用完析构函数,就回收内存呢?我也不知道为什么?,看方法test11的反汇编代码,的确是在方法返回的时候才回收内存?

那个疑问的源码如下:

复制代码
#include "stdafx.h" 
#include <iostream> 
using namespace std;
   
struct Point
{
    char arr[10];
    Point()
    {
        for(int i=0;i<9;i++)
        {
            arr[i]='a';
        }
        arr[9]='\0';
    }
    ~Point(){}
    operator char*()
    {
        return arr;
    }
};

void test11()
{
    char* ptr;
    {
        Point p;
        ptr=p;
    }
    cout<<ptr<<endl;
}
  
int _tmain(int argc, _TCHAR* argv[])
{    
    {  
         test11(); 
    } 
    system("pause");
    return 0; 
}
复制代码

test11的反汇编代码如下:

复制代码
void test11()
{
010431F0  push        ebp  //ebp表示栈顶指针
010431F1  mov         ebp,esp  //esp表示栈当前指针
009C31F3  push        0FFFFFFFFh  
009C31F5  push        offset __ehhandler$?test11@@YAXXZ (9CA3C8h)  
009C31FA  mov         eax,dword ptr fs:[00000000h]  
009C3200  push        eax  
009C3201  sub         esp,0E4h  
009C3207  push        ebx  
009C3208  push        esi  
009C3209  push        edi  
009C320A  lea         edi,[ebp-0F0h]  
B::`scalar deleting destructor':
009C3210  mov         ecx,39h  
009C3215  mov         eax,0CCCCCCCCh  
009C321A  rep stos    dword ptr es:[edi]  
009C321C  mov         eax,dword ptr [___security_cookie (9CF070h)]  
009C3221  xor         eax,ebp  
009C3223  mov         dword ptr [ebp-10h],eax  
009C3226  push        eax  
009C3227  lea         eax,[ebp-0Ch]  
009C322A  mov         dword ptr fs:[00000000h],eax  
    char* ptr;
    {
        Point p;
009C3230  lea         ecx,[p]  
009C3233  call        Point::Point (9C1541h)  
009C3238  mov         dword ptr [ebp-4],0  
        ptr=p;
009C323F  lea         ecx,[p]  
009C3242  call        A::~A (9C1546h)  
009C3247  mov         dword ptr [ebp-18h],eax  
    }
009C324A  mov         dword ptr [ebp-4],0FFFFFFFFh  
009C3251  lea         ecx,[p]  
009C3254  call        A::`scalar deleting destructor' (9C154Bh)  
    cout<<ptr<<endl;
009C3259  mov         esi,esp  
009C325B  mov         eax,dword ptr [__imp_std::endl (9D039Ch)]  
009C3260  push        eax  
009C3261  mov         ecx,dword ptr [ebp-18h]  
009C3264  push        ecx  
009C3265  mov         edx,dword ptr [__imp_std::cout (9D03A0h)]  
009C326B  push        edx  
009C326C  call        std::operator<<<std::char_traits<char> > (9C132Fh)  
009C3271  add         esp,8  
009C3274  mov         ecx,eax  
009C3276  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (9D0390h)]  
009C327C  cmp         esi,esp  
009C327E  call        @ILT+960(__RTC_CheckEsp) (9C13C5h)  
}
009C3283  push        edx  
009C3284  mov         ecx,ebp  
009C3286  push        eax  
009C3287  lea         edx,[ (9C32C0h)]  
009C328D  call        @ILT+350(@_RTC_CheckStackVars@8) (9C1163h)  
009C3292  pop         eax  //pop开始出栈  注意;这里才开始回收内存 
09C3293   pop         edx  
009C3294  mov         ecx,dword ptr [ebp-0Ch]  
009C3297  mov         dword ptr fs:[0],ecx  
009C329E  pop         ecx  
009C329F  pop         edi  
009C32A0  pop         esi  
009C32A1  pop         ebx  
009C32A2  mov         ecx,dword ptr [ebp-10h]  
009C32A5  xor         ecx,ebp  
009C32A7  call        @ILT+65(@__security_check_cookie@4) (9C1046h)  
009C32AC  add         esp,0F0h  
009C32B2  cmp         ebp,esp  
009C32B4  call        @ILT+960(__RTC_CheckEsp) (9C13C5h)  
009C32B9  mov         esp,ebp  //栈顶指针和栈当前指针指向同一个地址,即栈的长度就是一个指针的长度
009C32BB  pop         ebp  //栈顶指针弹出,现在栈空了
009C32BC  ret  
复制代码

我有这个疑问的原因就是:我以为在出作用域的时候不仅调用析构函数,还要回收内存,其实只是调用析构函数,内存在方法返回的时候才回收。



本文转自啊汉博客园博客,原文链接:http://www.cnblogs.com/hlxs/p/3415301.html

目录
相关文章
|
4月前
|
NoSQL Java Redis
Redis系列学习文章分享---第十八篇(Redis原理篇--网络模型,通讯协议,内存回收)
Redis系列学习文章分享---第十八篇(Redis原理篇--网络模型,通讯协议,内存回收)
69 0
|
2月前
|
存储 NoSQL 算法
Redis内存回收
Redis 基于内存存储,性能卓越,但单节点内存不宜过大,以免影响持久化或主从同步。可通过配置 `maxmemory` 限制最大内存。内存达到上限时,Redis采用两种策略:内存过期策略和内存淘汰策略。过期策略包括惰性删除和周期删除,后者分为 SLOW 和 FAST 模式。内存淘汰策略有八种,如 LRU、LFU 和随机淘汰等,用于在内存不足时释放空间。官方推荐使用 LFU 算法。
Redis内存回收
|
2月前
|
JavaScript 前端开发 算法
js 内存回收机制
【8月更文挑战第23天】js 内存回收机制
33 3
|
2月前
|
关系型数据库 MySQL
MySQl优化:使用 jemalloc 分配内存
MySQl优化:使用 jemalloc 分配内存
|
1月前
|
数据安全/隐私保护 虚拟化
基于DAMON的内存能回收 【ChatGPT】
基于DAMON的内存能回收 【ChatGPT】
|
2月前
|
缓存 Java 编译器
Go 中的内存布局和分配原理
Go 中的内存布局和分配原理
|
3月前
|
存储 缓存 算法
(五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析
在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内。而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程、Java对象在内存中的布局以及对象引用类型。
|
3月前
|
NoSQL Redis C++
c++开发redis module问题之在复杂的Redis模块中,特别是使用第三方库或C++开发时,接管内存统计有哪些困难
c++开发redis module问题之在复杂的Redis模块中,特别是使用第三方库或C++开发时,接管内存统计有哪些困难
|
3月前
|
Java 运维
开发与运维内存问题之在堆内存中新创建的对象通常首先分配如何解决
开发与运维内存问题之在堆内存中新创建的对象通常首先分配如何解决
19 1
|
2月前
|
存储 NoSQL Java
Tair的发展问题之Tair对于不同存储介质(如内存和磁盘)的线程分配是如何处理的
Tair的发展问题之Tair对于不同存储介质(如内存和磁盘)的线程分配是如何处理的

热门文章

最新文章

下一篇
无影云桌面