持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天点击查看活动详情
内存
内存分区模型
内存大致分为四个区域 在代码运行前就存在 代码区和全局区 在运行之后才有堆区和栈区
代码区:存放函数体的二进制代码 由操作系统进行管理
存放了CPU的执行的机器指令(就是把你在编译器内写的代码翻译成一个机器可以执行的二进制命令 存放在代码区 应该就是.out文件)具体的过程看 从cpp到exe 一般类的方法或者全局函数这些就放在代码区
代码区特点:
**共享性:**代码区的内容是对于外界是共享的 共享的目的是对于频繁被执行的程序,只需再内存中有一份代码即可 **只读性:**防止外界修改指令
全局区(静态/全局存储区):存放全局变量和静态变量以及常量
该区的数据在程序结束后由操作系统释放 也可以算作常量区 那什么是常量? 全局区分为两个部分 .data区和.bss区
.data区
全局初始化了的非0全局变量(包括静态全局变量 静态局部)
.bss区
全局未初始化或者为0的全局变量
那么如果本来我没有初始化全局变量a 他现在在bss区 然后我给他赋值了 他在哪呢 其实他还在bss区 不会发生改变了
静态区
栈区:由编译器自动分配释放 存放函数的参数值 局部变量等(从下往上 内存地址不断减小)
堆是操作系统所维护的一块特殊内存
大小
一般在32位操作系统中栈默认内存是1M
分配方式
采用先进后出的方式 从而避免了碎片问题 也就是函数的参数值和局部变量放在相应函数中 如果函数结束则编译器自动释放这两个变量的地址 存在一个规则:先进后出 也就是说 如果在创建类对象的时候 先创建a1 再创建a2 在销毁的时候 会先执行a2的析构函数 再执行a1的析构函数
栈溢出是什么鬼?
官方点就是程序向栈中某个变量写入的字节数超过了这个变量本来申请的字节数 导致栈中与其相邻的变量值发生了改变
栈溢出的原因有哪些呢
局部数组过大 递归调用层次太多 例如递归时执行压栈操作 压栈次数太多 指针或数组越界
栈的一些理解
栈中的数组
栈的地址是不断递减的 但是如果一个数组在栈中建立 他的地址还是递增的
堆区:由程序员分配和释放 也可程序结束时由操作系统回收(从上往下 内存地址不断增大)
大小
一般在32位操作系统中堆内存可以达到4G
分配方式
堆区的内存分配方式一般是在存储区中找符合大小的连续地址进行分配 这也导致了堆或造成空间不连续和碎片问题
new
new就是在堆区开辟内存 new返回的是该数据类型的指针 int * p = new int(10) 创建一个变量存放10 int * arr = new int[10] //这里的10是代表数组有10元素 delete p 就是手动释放 delete[] arr手动释放数组
实际上new有三种情况
new operator
平时最经常用的new T *ptr = new T(),分配内存,调用构造函数
class A
{
public:
A(int i) :a(i){}
private:
int a;
};
int main()
{
A* example = new A(1);
}
new operator实际上执行了以下三个步骤: 调用operator new分配内存,operator new (sizeof(A)) 调用构造函数生成类对象,A::A() ,调用placement new 返回相应指针
operator new
operator new不调用构造函数,而仅仅分配内存 有两个版本,前者抛出异常,后者当失败时不抛出异常,而是直接返回:
(1) void* operator new (std::size_t size);//分配size字节的存储空间,如果成功的话返回一个非空指针,将对象类型进行内存对齐,指向分配空间第一个字节。如果失败的话,会抛出bad_alloc异常,不调用构造函数
(2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;//和第一种一样,差别在于,如果失败的话,不抛出异常,而是返回一个null指针,不调用构造函数
(3) void* operator new (std::size_t size, void* ptr) noexcept;//只是返回ptr指针,并不分配内存空间。这里的ptr应该指向先前已经分配好的空间,这里的new调用对象的构造函数,在ptr指向的内存空间构造对象或对象数组。ptr指向的内存只要不释放,可以重复使用,所以这种用法一般在对象池或内存池实现中使用也就是placement new版本
placement new
placement new仅在一个已经分配好的内存指针上调用构造函数 placement new构造起来的对象或其数组,要显示的调用他们的析构函数来销毁,千万不要使用delete 它做的唯一一件事情就是调用对象的构造函数
new的使用方法
//可以在new后面直接赋值
int *p = new int(3);
int q = *new int;//但是这个试了一下 好像并不是在堆区 好像会被回收
student *stlist = new student[3]{{"abc", 90}, {"bac", 78}, {"ccd", 93}};
堆空间数组申请方式
int**a = new int*[3]{0};
for(int i = 0;i<3;i++)
{
a[i] = new int[4]{0};
}
/*删除*/
for(int i =0;i<3;i++)
{
delete a[i];
}
delete[]a;
堆栈的区别
堆寻址从低到高 栈从高到低 堆由程序员自己分配释放 栈由操作系统自动分配释放 堆频繁分配释放会产生碎片程序越来越慢 栈先进后出的特性不会产生碎片 堆分配效率低 栈分配效率高 造成这样效率不同的原因是: 栈是操作系统提供的数据结构 有专门的寄存器 堆是c++提供的 需要各自分配内存的算法 也就是堆每次分配都需要计算
分四个区的意义
在不同的区域内 数据的生命周期会不同 使编程更为灵活
自由存储区
自由存储是C++中通过new和delete动态分配和释放对象的抽象概念 堆是操作系统所维护的一块特殊内存 new 操作符从自由存储区上为对象动态分配内存,而 malloc 函数从堆上动态分配内存。(自由存储区不等于堆)
注意
一个空类对象分配的地址空间是1 如果该对象中有一个指针 就是4
例子
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
}