内存分布与分配方式
内存分布
C++中,内存分为5个区,分别是 堆 栈 自由存储区 全局/静态存储区 常量存储区
- 栈 : 内存由编译器在需要时自动分配和释放。通常用来存储局部变量和函数参数。
- 堆 : 内存使用new进行手动分配使用delete或delete[]手动释放。若未将内存正确释放,就会造成内存泄漏,但在程序结束时,会由操作系统回收.
- 自由存储区 : 使用malloc进行分配,使用free进行回收.与堆类似.
- 全局/静态存储区 : 全局变量和静态变量被分配到同一块内存中
- 常量存储区:存储常量,不允许被修改
分配方式
- 从静态区分配. 一般都全局变量和static类型变量
- 从栈区分配. 一般是局部变量,会随所在函数的结束而自动释放
- 从堆中分配. 一般是手动分配,通过 malloc或者new申请的空间,相应的,需要手动释放.free和delete.
栈和堆的区别
管理方式上:
- 对于栈来说,是由编译器自动管理的.无需手动控制;
- 对于堆来说,需要程序员手动进行管理,不然很容易造成内存泄漏问题.
空间大小上:
- 一般来说,对于32位系统下,堆内存空间可以达到4G(2的32次方).从这个角度来看堆的空间是非常大的. 而对于栈空间来说,大小是有限的. 大概2MB.
碎片化问题:
- 对于堆来说,频繁的new/delete操作会造成内存空间的不连续.从而产生大量的碎片.是的程序效率降低.
- 对于栈不存在这个问题.栈是先进后出的. 在某个内存弹出之前,它前面的内容肯定已经被弹出了.
生长方向:
- 对于堆来说,生长方向是向上的. 也就是向着内存地址增长的方向.
- 对于栈来说,生长方向是向下的.向着内存地址减小的方向增长;
分配方式:
- 堆都是动态分配的. 没有静态分配的堆.
- 栈的分配方式有两种: 静态分配和动态分配.静态分配是由编译器完成的.比如局部变量的分配. 动态分配是由alloca函数进行的.但它会由编译器自行释放.
分配效率:
- 栈是机器系统提供的数据结构. 计算机会在底层提供支持.分配专门的寄存器存放栈的地址. 压栈和出栈都有专门的指令执行.这就决定了栈的效率是比较高的.
- 堆是由C/C++函数库提供的.机制比较复杂.例如分配一块内存,那么库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能由于碎片化太多).就可能调用系统功能进行增加程序数据段的内存空间.这样才能得到足够大小的空间.
- 显然,堆的效率是低于栈的.
malloc/free与new/delete的区别
[new/delete和malloc/free] https://blog.csdn.net/hackbuteer1/article/details/6789164
[细说Malloc与new的区别] http://www.cnblogs.com/QG-whz/p/5140930.html
相同点:
- 都可用于申请动态内存和释放内存
- new/delete的功能完全覆盖了malloc/free
为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存
不同点:
- 操作对象不同
- malloc与free是C++/C 语言的标准库函数,new/delete 是C++的运算符
- 对于非内部数据类的对象,光用malloc/free无法满足动态对象的要求.
- 对象在创建的同时要自动执行构造函数, 对象消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加malloc/free。
- 用法上不同
- New 自动计算需要分配的空间. 而Malloc需要手动计算.
- New是类型安全的 而Malloc需要强转.(void* 编译器无法指出错误).
- new将调用构造函数,而malloc不能;delete将调用析构函数,而free不能。
- 本质上区别
- malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。
- 对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。
new / delete / operator new / operator delete / placement new / placement delete
- new 操作符做了两件事情:
- 申请内存空间
- 进行初始化
- operator new 类似于C中malloc,只负责分配内存空间
void *buffer = operator new(sizeof(string));
- placement new: 定位new, 用于在给定的内存中初始化对象.
已分配好的内存可以反复利用,有效的避免内存碎片问题。
例如:
用operator new分配了内存空间,然后后placement new进行初始化
void *buffer = operator new(sizeof(string));
buffer = new(buffer) string(“abc”);
new operator 可以分解 operator new 和 placement new 两个动作
内存对齐
[五分钟搞定字节对齐] https://blog.csdn.net/hairetz/article/details/4084088
字节对其的作用
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
- 性能原因:经过内存对齐后,CPU的内存访问速度大大提升
普通程序员中心目中的内存:由一个个字节组成的.而CPU不是这么看待的.
CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度).“内存读取粒度”
字节对齐的原则
- 数据成员对齐规则 :
- 结构体作为成员
- 按结构体中的基本类型的最大字节数对齐.sizeof结果必须是其整数倍
#pragma pack()
- 如 #pragma pack(1),告诉编译器所有的对齐按一个字节的整数倍来对齐.
union中的所有成员起始地址都是一样的
- union的一个作用就是用测试大小端:
- 小端: 数据的高字节保存在内存的高地址
- 大端: 数据的高字节保存在内存的低地址
0x1234数据的低字节是0x34.若char c的数据为0x34,则就是小端模式,否则为大端模式.
C语言中一个位域
大小为 1+1+1 =3 + 8 = 11 补成16字节
类对象的大小
- 类的静态成员不占据类的空间,成员函数也不占据类的空间大小;
- 内存对齐另外分配的空间大小,类内的数据也是需要进行内存对齐操作的
- 虚函数的话,会在类对象插入 vptr 指针,加上指针的大小;
- 虚函数表放在全局变量区
- 当该类是某类的派生类,那么派生类继承的基类部分的数据成员也会存在在派生类中的空间中,也会对派生类进行扩展。
C++成员函数在内存中的存储方式
每个对象所占用的存储空间只是该对象的数据部分(虚函数指针和虚基类指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间
- 静态成员函数和非静态成员函数都是在类的定义时放在内存的代码区的,因而可以说它们都是属于类的,
- 但是类为什么只能直接调用静态类成员函数,而非静态类成员函数(即使函数没有参数)只有类对象才能调用呢?
- 原因是类的非静态类成员函数其实都内含了一个指向类对象的指针型参数(即this指针),因而只有类对象才能调用(此时this指针有实值)。
- 不论成员函数在类内定义还是在类外定义,成员函数的代码段都用同一种方式存储。
- 成员函数的代码段都不占用对象的存储空间。