🍉内容专栏:【C/C++要打好基础啊】
🍉本文脉络:动态内存管理,内存分区模型,栈区堆区,new开辟内存
🍉本文作者:Melon西西
🍉发布时间 :2023.1.26
目录
堆区:由程序员分配和释放,如果程序员不释放,程序结束时由操作系统回收。
3.3由程序员管理的堆区,在C++中主要利用new在堆区开辟内存
3.1. malloc。void* malloc (size_t size);
3. 2.realloc,重新开辟内存。realloc函数可以做到对动态开辟内存大小的调整。
1.为什么要有动态内存管理:
用前面学的方法,int a=10;char arr[10]={0}这种,空间开辟的大小是固定的。但是对于空间的需求,有时候需要的空间大小在程序运行的时候才能知道,这时候只能用动态内存分配了。
2.内存分区模型:
2.1C++程序在执行时,将内存大方向划分为四个区域:
代码区:存放函数的二进制代码,由操作系统进行管理
全局区:存放全局变量和静态变量以及常量
栈区:存放函数的参数值,局部变量等,由编译器自动分配释放
注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区:由程序员分配和释放,如果程序员不释放,程序结束时由操作系统回收。
内存四区的意义:不同区域存放的数据,赋予不同的生命周期,让编程更灵活。
2.2.程序执行前:
程序执行前又代码区和全局区,栈区和堆区在程序运行后才有!!
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:
代码区:存放CPU执行的机器指令;
代码区是共享的内存中只有一份代码即可,也是只读的,防止意外修改。
全局区:该区域的数据在程序结束后由操作系统释放。
3.3由程序员管理的堆区,在C++中主要利用new在堆区开辟内存
利用new关键字,可以将数据开辟到堆区,利用delete释放
用栈上的指针保存了堆区存放10的地址编号,当在解引用*p的时候也会拿到10
void * func() { int * p = new int (10) ; return p ; } int main() { int * p = func(); cout<< *p<<endl; //输出为0 delete p ; //释放p }
int * arr = new int [10] ; //10代表数组有十个元素,arr返回的是数组首地址。
delete arr ;
3、动态内存函数:
栈区: 局部变量,函数的形式参数。
堆区: malloc/free,malloc,realloc,动态内存分配。
静态区:全局变量,静态变量。
3.1. malloc。void* malloc (size_t size);
函数向内存申请一块连续可通的空间,并返回指向这块空间的指针。如果开辟成功,则返回一个指向开辟好空间的指针;如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。两种写法:
l
3. 2.realloc,重新开辟内存。realloc函数可以做到对动态开辟内存大小的调整。
假设40内存不够用了,想扩容成80空间放20个int元素:
relloc函数会把前面的东西拷贝到新地址上,同时把旧的空间free掉。
如果扩容失败会返回空指针,为了防止前面东西丢失,后面用if语句再判断一下扩容后的是否为空
4.动态内存常见错误:
4.1.对空指针解引用的操作
解决办法:对函数的返回值进行判断,如果=NULL直接返回不往下走。
4. 2.对动态内存开辟空间的越界访问。
动态内存开辟空间和数组相似,都是在内存开辟一块连续的空间,访问的超出范围就会越界访问。
解决办法:自己仔细检查,编译器不会帮你。
4.3.对非动态开辟内存使用free释放。
局部变量的内存编译器会自动释放。
4.4.使用free释放一块动态开辟内存的一部分。
解决办法:free必须指向动态开辟内存的起始部分。
4.5.对同一块动态内存多次释放。
4.6.动态开辟内存忘记释放(内存泄漏)。
5、例子
5.1.形参开辟空间被销毁问题:
1.
分析:str空指针传给形参p,给p开辟100个内存空间没问题。但是p是形参,出了Memory函数p就销毁了,找不到p的起始地址了,无法释放内存,内存泄漏。销毁后str又成了空指针,对空指针解引用,程序崩溃。
正确修改:不适用形参实参,使用传地址。
5. 2.返回栈空间地址的问题:
分析:非法访问,和上面的类似,char p是临时变量,出来函数被收回,Test可以得到p return回来的地址,但是没有使用权限了,此时str指向一块空空间,成了野指针。栈空间地址不能随意返回,和变量不一样。
写在最后: