首先通过一些题目的引入讲解带大家走进C/C++的内存分布。
eg1:
根据上述变量的定义,来判断它们所在的内存位置。
从接下来的4个选项中选出最佳答案填入(注:可重复选)。
A、栈
B、堆
C、数据段
D、代码段
(1)shelter 在哪里?____;
(2)endanger 在哪里?____;
(3)stake 在哪里?____;
(4)substitute 在哪里?____;
(5)transfer 在哪里?____;
(6)federal 在哪里?____;
(7)*federal 在哪里?____;
(8)export 在哪里?____;
(9)*export 在哪里?____;
(10)slip 在哪里?____;
(11)*slip 在哪里?____。
eg2:
算出上述部分变量的大小或者长度。(以32位平台下的程序为标准)
(1)sizeof(transfer) = ____;
(2)sizeof(federal) = ____;
(3)strlen(federal) = ____;
(4)sizeof(export) = ____;
(5)strlen(export) = ____;
(6)sizeof(slip) = ____;
附:
C/C++中程序内存区域划分图
说明:
① 栈又叫堆栈,用来存放非静态局部变量、函数参数以及返回值等等。(栈是向下增长的)
② 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
③ 堆用于程序运行时动态内存分配。(堆是可以向上增长的)
④ 数据段是用于存储全局数据和静态数据段。
⑤ 代码段是用于存储可执行的代码或者只读常量这些的。
给了点提示,接下来xdm就只需要认真思考下,我这里插播几张图作为缓冲区后再公布答案。
正确答案:
第一题:
(1)~(5):C C C A A
(6)~(11):A A A D A B
第二题:
(1):40;
(2):5;
(3):4;
(4):4;
(5):4;
(6):4;
不出意外的话,像我这样子的水货肯定是全错,像xdm这样子的水平肯定是全对了。
答案解析:
第一题:
(1)~(5)这部分可能存在疑惑的就是第三题这个变量明明是定义在局部为什么不是在栈区而是在数据段上面,出现这个问题的极大概率可能是忘记了static关键字的作用,使其被修饰的变量变成静态变量,所以在内存中的位置是在数据段。不过我觉得大部分的xdm选错肯定是因为手抖了。
(6)~(11)这部分问题较大的应该会是第七题,(*federal)为什么是在栈上而不是在数据段上,原因在于federal是在栈上开辟了五个byte的空间把字符串拷贝进去(别忘记有\0的存在),因此(*federal)不是常量字符串,而是在栈上开辟的临时空间。其次有问题的可能会是第九题,要记住字符串是常量,放在数据段上面的。然后第十一题是通过malloc函数动态向内存堆中申请开辟空间,这个没什么好解释的,选错的xdm肯定是手抖或者眼花了。
第二题:
附:
由于这个题是以32位平台下为标准的,所以指针大小是4;如果是64位平台下,指针的大小就是8而不是4。
(1)~(6)这部分可能有问题会是第一题,因为是指定开辟10个int类型大小的数组空间,所以是40个字节。其次是第二、三题这样子的,记住sizeof算空间的时候,会把\0算进去,但是strlen算字符长度的时候,遇到\0就终止,所以答案是5和4。像我就经常搞混,以xdm的机智肯定是分分钟秒杀这种小题。
在C语言中,主要的动态内存管理方式就是:
malloc/calloc/realloc 和 free
还是用题的方式带xdm回顾下C语言的动态内存管理方式。
题目代码如下:
问题一:malloc/calloc/realloc的区别是什么?
问题二:这里需要free(p2)吗?
附:
MSDN下这些函数的解析:
① malloc
② calloc
③ realloc
楼主的英语也不好,但是通过日积月累的学习也是能看懂这上面的函数介绍,我觉得兄弟们看懂肯定也是绰绰有余,提示已经给了,接下来还是插播几张图后公布答案。
答案解析:
题一:
① malloc是随机地向堆区动态申请开辟内存空间,不会将开辟好的内存空间初始化;
② calloc则是在malloc的基础上,并将这些开辟好的内存空间都按字节序列初始化为0,通俗易懂理解就是初始化以后都是指向空指针。calloc的作用就相当于malloc配合着memset一起使用(即calloc = malloc + memset);
③ realloc比较特殊,它是向编译器申请开辟一片连续空间区域的扩容,而不同于malloc和calloc那样随机申请位置扩容。(即realloc是不会间断的一片空间区域,而malloc和calloc是能允许间断的空间区域)
编译器会根据其具体的情况选择进行原地扩容还是异地扩容,当你用realloc原先申请的那一块区域不足以满足你当前内存需求时,编译器就会采用异地扩容的方式,将原先那块内存空间的数据拷贝过去到一个新的足够大的空间里去。(realloc通常在写数据结构中的顺序表的时候常用)
为了加深xdm的印象,我再图示给你看。
题二:
基于题一理解的基础之上,我们就能知道这里是可以不需要free(p2)的,因为这块连续的空间并未使用,而用calloc申请开辟的内存空间默认都会按字节序列初始化为0,即指向的都是空指针。
不出意外的话,以xdm的水准肯定又是狠狠拿下!
前言:C语言的内存管理方式在C++中仍然还是可以继续使用的,只是有些地方就无能为力而且使用起来比较麻烦(在自定义类型这方面尤其明显,接下来会详细介绍)。对此,C++在C语言的基础之上又诞生出了自己的内存管理方式——通过new和delete操作符进行动态内存管理。
切记:new和delete是操作符而不是像C语言那样动态开辟是库函数。
(1)new和delete操作内置类型
注意:申请和释放单个元素的空间,配合使用的是new和delete操作符;申请和释放连续的空间,配合使用的是new[]和delete[]。
需谨记搭配使用不要搞混,不然编译器可能就会出现报错,毕竟这本身就是错误的写法,“常在河边走,哪有不湿鞋!” 如果编译器不报错,不要抱有侥幸心理认为自己就是对的。
(2)new和delete操作自定义类型
注意:在申请自定义类型的内存空间时,C++中的new会调用构造函数,delete会调用析构函数;而C语言中的malloc与free这样子的是都不会调用的。
附:
拓展内容:
C++提出new和delete操作符用于管理内存主要是用来解决两个问题:
① 对于自定义类型对象自动申请的时候,初始化和清理的问题。(new/delete)会自动调用构造函数和析构函数。
② new失败了以后要求抛异常处理,这样才能符合面向对象语言出错的处理机制。
注:delete和free一般是不会释放内存空间失败的,若失败了,通常都是由于释放空间上存在越界或者释放的指针位置不对。
(1)operator new 与 operator delete函数
简介:new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new 在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete 全局函数来释放空间。
具体需要通过观察底层原理的方式来进一步理解,这对于我们初学者太过于深入,容易把简单的问题复杂化,因此这边是直接给结论。
即:operator new 实际也是通过 malloc 来申请空间,如果 malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施;如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
通俗易懂地理解就是 operator new 和 operator delete 就是对 malloc 和 free 封装。
ooperator new 中调用 malloc 申请内存,若失败以后,就会改为抛异常处理错误,这样就很符合C++面向对象语言错误的处理方式。
(2)operator new 与 operator delete 的类专属重载
下面代码演示了,针对链表的节点ListNode通过重载类专属 operator new /operator delete,实现链表节点使用内存池申请和释放内存,提供效率。
其实内存池就是所谓的池化技术,何为池化技术呢?用个比较通俗易懂的例子方便大家理解,大家从小就应该听过一个和尚挑水喝、两个和尚抬水喝、三个和尚没水喝的故事吧!和尚们只能通过下山挑水才能有水喝,而且一次还只能挑一桶,非常费时费力;而若直接能在居住的寺庙旁建个水井的话,就能非常轻松挑水喝了,池化技术正是如此!
(1)内置类型
若申请的是内置类型的空间,new 和 malloc,delete 和 free 基本类似,不同的地方是:new 和 delete 申请和释放的是单个元素的空间,new[] 和 delete[] 申请的是连续空间,而且 new 在申请空间失败时会抛异常,而 malloc 是会返回NULL。
(2)自定义类型
new 的原理:
① 调用 operator new 函数申请空间。
② 在申请的空间上执行构造函数,完成对象的构造。
delete 的原理:
① 在空间上执行析构函数,完成对象中资源的清理工作。
② 调用operator delete函数释放对象的空间。
new T[N] 的原理:
① 在空间上执行析构函数,完成对象中资源的清理工作。
② 调用 operator delete 函数释放对象的空间。
delete[] 的原理
① 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。
② 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间。
① 概述:定位 new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
② 使用格式:
new(place_address)type 或者 new(place_address)type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
③ 使用场景:
定位 new 表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用 new 的定义表达式进行显示调析构造函数进行初始化。
代码演示讲解:
(1)malloc / free 和 new / delete 的区别:(从用法上和底层上分析下手)
malloc / free 和 new / delete 的共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同点是:
① malloc 和 free 是函数,new 和 delete 是操作符。
② malloc 申请的空间不会初始化,而 new 是可以初始化的。
③ malloc 申请空间时,需要手动计算空间大小并传递,而 new 只需要在其后跟上空间的类型即可。
④ malloc 的返回值为 void*,在使用时必须强转,new 不需要,因为 new 后跟的是空间的类型。
⑤ malloc 申请空间失败时,返回的是NULL,因此使用时必须判空;而 new 不需要,但是 new 需要捕获异常。
⑥ 申请自定义类型对象时,malloc / free 只会开辟空间,不会调用构造函数与析构函数,而 new 在申请空间后会调用构造函数完成对象的初始化,delete 会在释放空间前调用析构函数完成空间中资源的清理。
(2)内存泄露:
① 什么是内存泄漏?② 内存泄露的危害是什么?
① 内存泄漏的定义:内存泄漏是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
② 内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,比如操作系统、后台服务等等,若一直出现内存泄漏并未及时处理,就会导致响应越来越慢,最终卡死。
代码演示讲解内存泄漏:
③ 内存泄漏的分类:
C/C++程序中一般我们关心两种方面的内存泄漏:
【1】堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据需要分配通过 malloc / calloc / realloc / new 等从堆中分配的一块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生堆内存泄漏(Heap leak)。
【2】系统资源泄漏
指的是程序使用系统分配的资源,比如套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
④ 如何避免内存泄漏?
【1】工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个是理想状态。但是如果碰上抛异常时,就算注意释放了,还是可能会出现问题。这就需要下一条智能指针来管理才有保证。
【2】采用RAII思想或者智能指针来管理资源。
【3】有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
【4】出问题了使用内存泄漏的工具检测。ps:不过很多工具是不靠谱的,或者收费比较昂贵。
C/C++内存管理讲解就此完结,楼主不才,不喜勿喷,若有错误或需要改进的地方,非常感谢你的指出,我会积极学习采纳。谢谢家人们一直以来的支持和鼓励,我会继续努力再接再励创作出更多优质的文章来回报家人们的。编程爱好的xdm,若有编程学习方面的问题可以私信我一同探讨(我尽力帮),毕竟“众人拾柴火焰高”,大家一起交流学习,共同进步!
2023年3月11日