目录
二、一个程序都有什么部分,分别的作用是什么,程序启动的过程是什么,怎么判断数据是分配到堆上还是栈上?
一、C++的内存管理
1、内存的分配方式
在C++中,内存分成五个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
栈 在执行函数时,函数内部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放;
堆 由malloc和free申请和释放的内存;
自由存储区 由new和delete申请和释放的内存;
全局/静态存储区 都在同一块静态存储区
常量存储区 这块比较特殊,不需要修改
2、简述一下堆和栈的区别
区别:
1、堆栈空间分配不同 栈由操作系统自动分配和释放,存放函数的参数值、局部变量值等;而堆由程序员自动分配和释放。
2、堆栈缓存方式不同 栈使用的是一级缓存,他们通常都是在被调用时处于存储空间,调用完毕立即释放;堆存放在二级缓存中,速度要慢些。
3、堆栈数据结构不同 堆类似于数组结构,栈类似于栈结构,先进后出。
3、常见的内存错误及解决方法
(1)内存未分配成功,却使用了它
解决:定义指针时先初始化为NULL。在使用指针之前先初始化为NULL,如果是malloc或者new分配的内存,一定要先检查if(p!=NULL)或者if(p==NULL)进行检查。
(2)内存虽然分配成功,但是尚未初始化就引用了它
解决:很多时候我们总以为分配内存后操作系统系统会给他赋初值,但是很多时候并不是这样的,为了保险起见,我们在分配内存的时候最好给它赋上初值。
(3)内存分配成功并且已经赋初值,但是操作越过了内存边界
解决:避免数字或者指针的下标越界,特别要当心发生“多1”或者“少1”的操作,尤其是在for循环时,特别容易进行多1的操作,这块就得提醒我们小心点了。
(4)忘记释放内存,造成内存泄漏
解决: 动态内存的申请和释放必须配对,防止内存泄漏。但有些时候我们只进行了malloc或者new开辟了内存空间,并没有free或者delete进行释放,也没有发生什么故障,这是因为内存比较充足,但每调用一次函数,就减少一块内存,但毕竟内存是有限的,如果无休止的调用下去总会出现内存不够用的情况,直到程序死掉。
(5)释放了内存却继续使用它
解决:使用free或者delete释放了内存后应立即将指针设置为NULL,防止“野指针”。
4、什么是内存泄漏,有什么解决办法
内存泄漏:简单的说就是申请了一块内存空间,使用完毕没有释放掉;
比如说:(1)new和malloc申请资源使用后,没有用delete和free释放;
(2)子类继承父类时,父类的析构函数不是虚析构;
(3)Windows句柄资源使用后没有释放。
解决办法:
第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕就立马释放掉
第二:子类继承父类时,父类的析构函数修改成虚析构函数;
第三:使用智能指针;
第四:一些常见的工具帮助检测,如ccmalloc、Dmalloc、Leaky等等。
二、一个程序都有什么部分,分别的作用是什么,程序启动的过程是什么,怎么判断数据是分配到堆上还是栈上?
1、一个程序有哪些部分?
从上图可以看出,从低地址到高地址,一个程序由代码段、数据段、BSS段组成
1、数据段:存放程序中已经初始化的全局变量和静态变量的一块区域
2、代码段:存放程序执行代码的一段内存区域,只读。代码段的头部还会包含一些只读的常数变量
3、BSS段:存放程序中未初始化或者初始化为0的全局变量和静态变量的一块区域,可读写的,在程序执行之前BSS段会自动清0;
4、可执行程序在运行时又会多出两个区域:堆区和栈区
堆区heap:动态申请内存,堆从低地址向高地址增长;
栈区stack:存储局部变量、函数参数值,栈从高地址到低地址增长,是一块连续的空间。
5、文件映射区:位于堆和栈之间。
2、程序启动的过程是什么?
(1)操作系统首先创建相应的进程并分配私有的进程空间,然后操作系统的加载器负责把可执行文件的数据段和代码段映射到进程的虚拟内存空间中;
(2)加载器读入可执行程序的导入符号表,根据这些符号表可以查找出该可执行程序的所有依赖的动态链接库;
(3)加载器针对该程序的每一个动态连接库调用LoadLibrary;
(4)初始化应用程序的全局变量,对于全局对象自动调用构造函数;
(5)进入应用程序入口点函数开始执行。
3、怎么判断数据分配到栈上还是在堆上呢?
局部变量肯定是在栈上,而通过malloc和new申请的空间是在堆上。
三、内存对齐
1、内存对齐规则
1、第一个成员在与结构体变量偏移量为0的地址处,即结构体的首地址
2、其他成员变量要对齐到某个数字的整数倍的地址处;
3、结构体的总大小为最大对齐数的整数倍;
4、如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的大小就是所有最大对齐数的整数倍。
对齐数=该结构成员变量自身的大小与编译器默认的一个对齐数的较小值。
2、结构体的大小计算-三部曲
知道了结构体对齐规则,我们就可以进行内存对齐了
struct num{
int a;
char b;
double c;
};
第一步:找出每个成员变量的大小将其与编译器的默认对齐数相比较,取其较小值为该成员变量的对齐数
struct num{ //成员变量自身大小 默认对齐数 较小值
int a; //4 8 4
char b; // 1 8 1
double c;// 8 8 8
};
第二步:根据每个成员对应的对齐数画出他们在内存中的相对位置
第三步:通过最大对齐数决定最终该结构体的大小
通过图像我们发现,红色部分(int成员占的内存)+黄色部分(char成员占的内存)+灰色部分(double成员占的内存)+黄色与灰色之间的白色部分(浪费掉部分)总共占了16个字节。
我们需要将他们总共占用的内存空间16与结构体成员最大最齐数(8)相比较,结构体的总大小为最大对齐数的整数倍,此时16正好是8的整数倍,所以该结构体在这个编译器下的大小就为16个字节。