企业网站设计 优帮云,软件定制论坛,微信公众号对接网站,编程猫下载一、C/C内存分布
1. 内存分区 栈又叫堆栈–非静态局部变量/函数参数/返回值等等#xff0c;栈是向下增长的。内存映射段是高效的I/O映射方式#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存#xff0c;做进程间通信 .堆用于程序运行时动态内…一、C/C内存分布
1. 内存分区 栈又叫堆栈–非静态局部变量/函数参数/返回值等等栈是向下增长的。内存映射段是高效的I/O映射方式用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存做进程间通信 .堆用于程序运行时动态内存分配堆是可以上增长的数据段–存储全局数据和静态数据 也叫静态区代码段–可执行的代码/只读常量 也叫常量区 2. 程序运行时加载过程
首先我们平时所写的代码如test.cpp)存放在那
其实是存放在磁盘上的因为是文件形式
而运行一个程序的过程是怎么样的
写好的代码 - 编译链接 - 可执行程序
也就说我们写好代码点击运行就是运行的这个可执行程序
可执行程序(Windows下.exe、Linux下a.out)中包括
二进制指令代码CPU读取数据其他一些内容
而当程序运行时首先要加载哪些东西到内存呢
二进制指令代码 - 代码段常量数据 - 代码段全局变量 - 数据段 因为全局变量在main函数前已经定义好
堆栈上的数据何时创建 当二进制指令代码加载到代码段之后由CPU依次读取并执行二进制指令 当开始执行main函数时开始创建函数栈帧此时栈中开辟的变量和数据就开始定义了。 而堆上的数据也是在栈上通过malloc等动态开辟内存的函数来开辟的
注意
类、函数、符号表、公共代码区等概念都是在编译链接阶段的概念编译链接之后类、函数等都转变成了二进制指令加载到代码了不存在所谓的类或者函数了3. 常见数据类型在内存中的分布 二、动态内存管理 C/C中除了堆以外的内存分区中资源的申请与释放不用我们管系统会自动处理。所以内存泄漏等问题出现在堆上 1. C语言中动态内存管理方式 malloc : 申请一块空间 calloc 申请一块空间并初始化 realloc 对一段空间进行扩容 使用完都需要 free防止内存泄漏
void Test ()
{int* p1 (int*) malloc(sizeof(int));free(p1);
// 1.malloc/calloc/realloc的区别是什么int* p2 (int*)calloc(4, sizeof (int));int* p3 (int*)realloc(p2, sizeof(int)*10);// 这里需要free(p2)吗
// 注意: 不需要// 因为如果原地扩容释放p3就相当于释放了p2// 如果异地扩容realloc会自动完成原始空间的freefree(p3 );
}2. C动态内存管理 虽然C向下兼容C但是有些地方C的方式是无能为力的所以C又搞了一套自己的动态内存管理方式 即new 和 delete操作符
注意new和delete 不是函数是操作符 new一个空间使用delete释放 new多个空间使用delete [] 释放
① new和delete针对 内置类型
// C语言动态开辟
int* p1 (int*)malloc(sizeof(int));
if(p1NULL)
{perror(malloc fail);exit(-1);
}// C动态开辟//申请一个int类型大小的空间
int* p2 new int;//new自己去算int是多少字节//开辟多个int的空间 -- 也符合后定义的先析构
int* p3 new int[5];//开5个int大小的空间//申请一个int类型大小的空间并初始化
int* p4 new int(5); //申请一个int大小的空间并初始化为5//对new的数组初始化 {} C98不支持C11才支持
int* p5 new int[5]{ 1,2,3,}; //初始化为 1 2 3 0 0 不给的默认给0// 释放free(p1);//C的释放方式delete p2; // 针对new int
delete p4; // 和 new int()
delete[] p3; //针对new int[] 注意
new/delete 和 new[] /delete[] 一定要匹配否则有时候会出现崩溃。一般来说自定义类型一定会报错自定义类型一般不会报错
结论
1. 针对内置类型new/delete 和 malloc/free没有本质的区别只有用法上的区别new/delete只是用法简化了
2. malloc的需要去检查是否开辟成功new不需要如果失败默认会抛异常
② new和delete针对自定义类型
new和delete针对内置类型与C的malloc/free无大区别但是对于自定义类型区别很大 这也是引入new/delete的原因
new/delete针对自定义类型与malloc/free最大的区别
就是new的时候会自动调用 默认构造函数如果无默认构造会报错delete的时候会自动调用析构函数
1. 单个自定义类型对象的构造/析构
//C语言用mallocmalloc不会初始化
A* p1 (A*)malloc(sizeof(A));
if (p1 nullptr)
{perror(malloc fail);exit(-1);
}// C使用new有两步
// 1. new申请空间 2. 调用默认构造函数初始化
A* p2 new A;
A* p3 new A(10);// 显示传递参数来构造//释放空间的区别
//C语言的free只是释放该对象不会清理对象中的资源
free(p1);//C: 1.调用析构函数清理对象中的资源 2. 释放空间
delete p2;
delete p3;
多个对象初始化
多个对象默认构造初始化前提要提供默认构造函数
A* p4 new A[10];//默认构造10次
delete[] p4;多个对象显示构造初始化 /* 写法比较多 */A* p5 new A[10]{ 1,2,3,4,5 };//开10个前5个显示调用构造初始化后5个默认构造A* p6 new A[]{ 1,2,3,4,5 };//后面有几个数组就开几个对象的大小A* p7 new A[]{ (1),(2) };//用括号
A* p8 new A[]{ {1},{2} };//用花括号A* p9 new A[]{ A(1),A(2)};//用匿名构造最常用的是第一种和第二种 注意支持C11的编译器才可以显示构造初始化 VS2013就不支持2019以上是支持的
结论
new/delete是为自定义类型准备的不仅在堆上申请出来还会调用构造和析构函数初始化和清理 因为如果采用malloc申请自定义类型是无法进行初始化的因为构造函数是在对象定义的时候自动调用 malloc只是申请了空间无法直接调用构造函数进行初始化也没办法通过访问成员变量进行初始化因为一般都是私有的 ③ malloc与new失败时的区别
malloc失败时会返回NULL
new失败时会抛异常
测试代码:
//malloc失败
void test3()
{char* p1 (char*)malloc(1024u * 1024u * 1024u * 2);//u是为了防止整形溢出printf(%p\n, p1);//以地址形式打印出p1
}//输出结果 00000000
// 即malloc失败返回NULL
new失败
char* p2 new char[1024u * 1024u * 1024u * 2 - 1];//-1是因为规定不能超过0x7fffffff即整形的一半运行结果 所以malloc需要检查返回值看是否malloc失败
new是不需要检查返回值的失败会抛异常
3. operator new和 operator delete函数底层
我们知道new一个对象其实做了两件事
申请内存调用构造函数
那么new申请内存是调用了谁来申请内存呢
事实上new和delete是用户进行动态申请内存和释放操作符operator new 和 operator delete是系统提供的全局函数
new在底层调用operator new全局函数来申请空间
如图可见new操作符被解析为指令的时候主要是 1. call operator new函数 2. call A::A(构造函数) 注意operator new就是一个函数名不是运算符重载只是名字很挫
① operator new和operator delete的底层原理
operator new的源代码
/*
operator new该函数实际通过malloc来申请空间当malloc申请空间成功时直接返回申请空间
失败尝试执行空 间不足应对措施如果改应对措施用户设置了则继续申请否
则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* p;//如果malloc失败返回NULL 则抛异常while ((p malloc(size)) 0)if (_callnewh(size) 0){// report no memory// 如果申请内存失败了这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}可以看到operator new开空间实际上他也是调用了malloc函数来开空间只是operator new采用了如果失败就抛出bad_alloc的异常的做法。 这也是为什么要用operator new封装malloc而不直接采用malloc的原因 总结operator new的作用
帮助new开空间封装malloc以符合Cnew的失败机制抛异常
operator delete
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{_CrtMemBlockHeader* pHead;RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));if (pUserData NULL)return;_mlock(_HEAP_LOCK); /* block other threads */__TRY/* get a pointer to memory block header */pHead pHdr(pUserData);/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead-nBlockUse));_free_dbg(pUserData, pHead-nBlockUse);__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLYreturn;
}/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)operator delete是调用free来进行释放资源的 注意free实际上也是宏定义的原型是_free_dbg free是为了方便用户使用 /其他的一些加锁和检查可以忽略/ 所以new/delete对于内置类型其实和malloc/free没有本质区别因为从底层来看内置类型不需要调用构造函数和析构函数
总结new的过程 new Type - call operator new(malloc失败抛异常) - call Type构造函数
② 直接使用operator new开辟空间了解
operator new这个函数也可以自己单独使用用法和malloc完全一样不同的是operator相比于malloc会抛异常搭配抛异常的try catch使用
//operator new的使用try{char* ptr (char*)operator new(1024u * 1024u);//operator new开空间printf(%p\n, ptr);//打印出地址operator delete(ptr);}catch (const std::exception e){cout e.what() endl;}实际当中是不需要使用operator new 和 operator delete 直接使用new和delete即可。
③ operator new和operator delete的重载了解
1 利用重载找出内存泄漏
有时候我们不想用系统提供的operator new和operator delete
那么我们就可以进行重载我们自己的operator new和
operator delete来完成一些特殊的需要
如我们想要检测哪里存在内存泄漏这时候就可以自己重载一个operator new至于operator delete则自己写一个全局的不是重载 我们在自己重载的operator new中打印文件、函数、行号、申请字节数然后调用全局的 ::operator new 自己写一个全局的operator delete这样delete的时候就会先调用自己写的operator delete了 条件编译在Debug下new被宏定义为带着__FILE__,__FUNCTION__,__LINE__三个参数的new从而实现调用我们自己重载的operator new。C是允许这么玩的正常默认只有new的空间大小一个参数传给operator new 注__FILE__,__FUNCTION____LINE__等是C语言中的宏
分别是当前文件名当前函数名当前行号
// 重载operator new在申请空间时打印在哪个文件、哪个函数、第多少行申请了多少个字节
void* operator new(size_t size, const char* fileName, const char* funcName,size_t lineNo)
{void* p ::operator new(size);cout new: fileName - funcName - lineNo - p - size endl;return p;
}
//自己实现全局operator delete使用delete时就先调用自己写的operator delete
void operator delete(void* p)
{cout delete: p endl;free(p);
}// 重载operator delete这里用不到只是为了匹配重载的operator new来消除警告
void operator delete(void* p, const char* fileName, const char* funcName,size_t lineNo)
{cout delete: fileName - funcName - lineNo - p endl;::operator delete(p);
}// 使用条件编译和宏对调用进行简化
// 只有在Debug方式下才调用用户重载的 operator new
// 条件编译需要放在函数重载的下面
//使用 __FILE__这样的宏作为参数传给自己写的operator new来实现打印文件名等功能。
#ifdef _DEBUG
#define new new(__FILE__, __FUNCTION__, __LINE__)
#endif
//还需要在main的上面否则不进行替换程序就走完了int main()
{A* p1 new A;//new的时候 调用重载的operator newdelete p1;//delete时调用自己写的全局operator deleteA* p2 new A[4];//delete[] p2;A* p3 new A;delete p3;A* p4 new A;//delete p4;A* p5 new A;delete p5;return 0;
}
//main函数中 申请5个对象 但是只释放3个
// 要找出没有释放的两个程序运行结果如图
new了5个但是只delete了3个并且再通过行号就可以查出具体是哪里没释放而导致的内存泄漏了
2 重载一个类专属的operator解决频繁申请空间带来的空间碎片问题内存池 调用new编译器会转换为调用 operator new 构造函数 默认情况下operator new 使用全局库里面的 但是每个类可以实现自己专属的operator newnew 这个类对象就会调用这个自己实现的 我们知道如果一个类 比如ListNode类链表需要频繁地向内存申请空间 这样就容易造成一些内存碎片问题。这里就可以利用池化技术来减少内存碎片问题即内存池 内存池的工作原理 因为malloc是去向堆申请内存但是要知道操作系统是很忙的如果频繁的申请就会经常打断操作系统的资源的分配 而内存池就相当于中间的一个角色。内存池先申请一部分内存当你想要开辟空间首先到内存池中开辟当内存池中的 内存用完才会再去向堆上申请。这样就减少了请求操作系统的次数提高效率。 用个比喻的话就是堆是你妈妈的钱包内存池是你自己的钱包。你的定期生活费就是内存池提前申请的内存 我们可以在类中重载一个operator new和operator delete函数。注意不是函数重载也不是运算符重载只是命名空间即域不同在我们重载的函数中使用内存池可以自己写也可以调用STL或者Boost库或第三方库中的内存池
当new一个对象和delete一个对象的时候机制决定了会先去类中找有没有类专属的operator new 和 operator delete 局部就近原则
找到了就直接使用找不到才回去全局找。并且因为是类专属的其他的类的new/delete并不受影响
所以会直接调用我们在类中重载的operator new和operator delete函数也就是先去我们定制的内存池中申请内存而不会直接向堆上申请内存
这里演示STL中的内存池allocator空间配置器
allocate申请空间
deallocate释放空间
//重载类专属operator new
struct ListNode
{int _val;ListNode* _next;//内存池给所有ListNode用static allocatorListNode _alloc;//把内存池设置为静态成员变量声明//每个ListNode对象都可以访问到属于整个类void* operator new(size_t n){cout void* operator new(size_t n) - STL内存池allocator申请 endl;//allocator类void* obj _alloc.allocate(1);return obj;}void operator delete(void* ptr){cout void* operator delete(size_t n) - STL内存池allocator释放 endl;_alloc.deallocate((ListNode*)ptr,1);}ListNode(int val):_val(val), _next(nullptr){}};
//类外面定义内存池对象
allocatorListNode ListNode::_alloc;//默认构造
int main()
{//频繁申请ListNode 想提高效率 申请ListNode时不去malloc而是走自己定制的内存池ListNode* node1 new ListNode(1);ListNode* node2 new ListNode(1);ListNode* node3 new ListNode(1);delete node1;//delete node2;//这里还可以作内存泄漏的检测 申请和释放的个数不符合就是内存泄漏delete node3;return 0;
}很容易看出存在一个内存泄漏
ps:(其实更官方一些的检查内存泄漏的并不是这样一个个数而是用一个数据结构在operator new的时候把地址存进来
operator delete的时候把它删除最后进行查找剩下的就是没被释放的)
4. new/delete的实现原理
① 内置类型
如果申请的是内置类型的空间new和mallocdelete和free基本类似不同的地方是
new/delete申请和释放的是单个元素的空间new[]和delete[]申请的是连续空间而且new在申
请空间失败时会抛异常malloc会返回NULL
② 自定义类型
new的原理 调用operator new函数申请空间在申请的空间上执行构造函数完成对象的构造 delete的原理 在空间上执行析构函数完成对象中资源的清理工作调用operator delete函数释放对象的空间 new T[N]的原理 调用operator new[]函数在operator new[]中实际调用operator new函数完成N个对 象空间的申请在申请的空间上执行N次构造函数 delete[]的原理 在释放的对象空间上执行N次析构函数完成N个对象中资源的清理调用operator delete[]释放空间实际在operator delete[]中调用operator delete来释 放空间
5. 定位new表达式Replace new了解
我们知道构造函数的调用时机常见的就是
直接创建对象的时候自动调用构造函数初始化new对象的时候自动调用构造函数
但是上面两种方式都是在开空间创建对象的时候调用构造我们是不可以显示调用构造函数的可以显示调用析构函数
并且也不可以访问成员变量直接初始化
那么如何对已经分配好空间的对象调用构造函数来初始化呢 这就是定位new的作用
**定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象 **
但是析构的话需要自己显示调用
使用格式
new (place_address) type或者new (place_address) type(initializer-list) **place_address必须是一个指针initializer-list是类型的初始化列表 **
// 对malloc或者operator new的空间 用定位new调用构造函数初始化
int main()
{//p2现在指向的只不过是与A对象相同大小的一段空间还不能算是一个对象因为构造函数没// 有执行A* p2 (A*)malloc(sizeof(A));if (p2 NULL){perror(malloc fail);}//定位new 初始化已经开辟好的空间//new(p2)A; //不给参数new(p2)A(10);//给参数//free p2前需要手动析构释放资源p2-~A();free(p2);//free p2return 0;
} 使用场景
相比new来说new即开了空间还自动初始化为啥还要有定位new呢
这里就还是因为存在 内存池 的应用场景
如果我们去内存池申请空间那么内存池只是开空间并不会调用构造函数初始化
所以我们要对开好的空间进行初始化就必须使用定位new
扩展阅读 C重载 operator new 和 operator delete 实现一个简单内存泄漏跟踪器 文章转载自: http://www.morning.yixingshengya.com.gov.cn.yixingshengya.com http://www.morning.ksqyj.cn.gov.cn.ksqyj.cn http://www.morning.nxfwf.cn.gov.cn.nxfwf.cn http://www.morning.qggcc.cn.gov.cn.qggcc.cn http://www.morning.gjlxn.cn.gov.cn.gjlxn.cn http://www.morning.nyqnk.cn.gov.cn.nyqnk.cn http://www.morning.sfwfk.cn.gov.cn.sfwfk.cn http://www.morning.rlrxh.cn.gov.cn.rlrxh.cn http://www.morning.qpsdq.cn.gov.cn.qpsdq.cn http://www.morning.qbzdj.cn.gov.cn.qbzdj.cn http://www.morning.jsljr.cn.gov.cn.jsljr.cn http://www.morning.snnb.cn.gov.cn.snnb.cn http://www.morning.qtbnm.cn.gov.cn.qtbnm.cn http://www.morning.xlclj.cn.gov.cn.xlclj.cn http://www.morning.znqztgc.cn.gov.cn.znqztgc.cn http://www.morning.kyfrl.cn.gov.cn.kyfrl.cn http://www.morning.rdwm.cn.gov.cn.rdwm.cn http://www.morning.dfkby.cn.gov.cn.dfkby.cn http://www.morning.fbccx.cn.gov.cn.fbccx.cn http://www.morning.mpnff.cn.gov.cn.mpnff.cn http://www.morning.gyqnc.cn.gov.cn.gyqnc.cn http://www.morning.nclps.cn.gov.cn.nclps.cn http://www.morning.vaqmq.cn.gov.cn.vaqmq.cn http://www.morning.nrqtk.cn.gov.cn.nrqtk.cn http://www.morning.xdqrz.cn.gov.cn.xdqrz.cn http://www.morning.bdsyu.cn.gov.cn.bdsyu.cn http://www.morning.ghrhb.cn.gov.cn.ghrhb.cn http://www.morning.lkkkf.cn.gov.cn.lkkkf.cn http://www.morning.gwsll.cn.gov.cn.gwsll.cn http://www.morning.fnnkl.cn.gov.cn.fnnkl.cn http://www.morning.yqndr.cn.gov.cn.yqndr.cn http://www.morning.clxpp.cn.gov.cn.clxpp.cn http://www.morning.wcczg.cn.gov.cn.wcczg.cn http://www.morning.xzrbd.cn.gov.cn.xzrbd.cn http://www.morning.qkwxp.cn.gov.cn.qkwxp.cn http://www.morning.wchcx.cn.gov.cn.wchcx.cn http://www.morning.sgmis.com.gov.cn.sgmis.com http://www.morning.lcbnb.cn.gov.cn.lcbnb.cn http://www.morning.ityi666.cn.gov.cn.ityi666.cn http://www.morning.xtrnx.cn.gov.cn.xtrnx.cn http://www.morning.hsxkq.cn.gov.cn.hsxkq.cn http://www.morning.jhrqn.cn.gov.cn.jhrqn.cn http://www.morning.myrmm.cn.gov.cn.myrmm.cn http://www.morning.nmymn.cn.gov.cn.nmymn.cn http://www.morning.tcxk.cn.gov.cn.tcxk.cn http://www.morning.ykgp.cn.gov.cn.ykgp.cn http://www.morning.hbxnb.cn.gov.cn.hbxnb.cn http://www.morning.nzmw.cn.gov.cn.nzmw.cn http://www.morning.pwdmz.cn.gov.cn.pwdmz.cn http://www.morning.kpqjr.cn.gov.cn.kpqjr.cn http://www.morning.mcwrg.cn.gov.cn.mcwrg.cn http://www.morning.owenzhi.com.gov.cn.owenzhi.com http://www.morning.xcjwm.cn.gov.cn.xcjwm.cn http://www.morning.wfjrl.cn.gov.cn.wfjrl.cn http://www.morning.nfdty.cn.gov.cn.nfdty.cn http://www.morning.qbfkz.cn.gov.cn.qbfkz.cn http://www.morning.fhcwm.cn.gov.cn.fhcwm.cn http://www.morning.kfysh.com.gov.cn.kfysh.com http://www.morning.pprxs.cn.gov.cn.pprxs.cn http://www.morning.pprxs.cn.gov.cn.pprxs.cn http://www.morning.lxjcr.cn.gov.cn.lxjcr.cn http://www.morning.ygrkg.cn.gov.cn.ygrkg.cn http://www.morning.lpskm.cn.gov.cn.lpskm.cn http://www.morning.zfwjh.cn.gov.cn.zfwjh.cn http://www.morning.cgtrz.cn.gov.cn.cgtrz.cn http://www.morning.tndhm.cn.gov.cn.tndhm.cn http://www.morning.lswgs.cn.gov.cn.lswgs.cn http://www.morning.pzqnj.cn.gov.cn.pzqnj.cn http://www.morning.lqznq.cn.gov.cn.lqznq.cn http://www.morning.tkxyx.cn.gov.cn.tkxyx.cn http://www.morning.rkck.cn.gov.cn.rkck.cn http://www.morning.mpwgs.cn.gov.cn.mpwgs.cn http://www.morning.spftz.cn.gov.cn.spftz.cn http://www.morning.hffjj.cn.gov.cn.hffjj.cn http://www.morning.dyght.cn.gov.cn.dyght.cn http://www.morning.qrlkt.cn.gov.cn.qrlkt.cn http://www.morning.rpdmj.cn.gov.cn.rpdmj.cn http://www.morning.hgbzc.cn.gov.cn.hgbzc.cn http://www.morning.jpbpc.cn.gov.cn.jpbpc.cn http://www.morning.mcqhb.cn.gov.cn.mcqhb.cn