内存管理
1.1 内存分配方式
分配方式
C++中内存分5个区:
栈区 函数内局部变量在栈上创建,函数结束后这些内存自动回收 (分配效率高,但内存容量有限,windows的VS上默认1M,ubuntu上默认8M)
查看:
VS: 项目--属性--链接器--系统--堆栈保留大小
ubuntu:
ulimit -s (-a) 查看得到8192,修改的话 ulimit -s 8000 改为8000bytes
堆区 这是操作系统维护的一段内存,malloc等分配的内存块, 通过free释放.
自由存储区 这个C++内存的概念,借助new分配的内存块,程序结束前不会自动释放, 一个new对应一个delete,程序技术后,操作系统回收. 因为new的实现有很多版本,有的情况下new操作符的实现借助了malloc,而有的版本通过其他方式.这两种内存区域的运作方式不同、访问方式不同,所以应该被当成不一样的东西来使用。 C++ 自由存储区是否等价于堆? - 云+社区 - 腾讯云 (tencent.com)
全局/静态存储区 存储全局变量,静态变量.(以前C语言全局变量根据是否初始化又分为2个,C++里不区分)
常量存储区 存放常量,不允许需改. 读取的时候编译器可能只读一次,存放在栈中,后面用到不用再访问原地址.
堆和栈的区别
管理方式不同 堆需要手动管理,栈无需
空间大小不同()前面说过了)
碎片 堆频繁new,delete,势必容易造成内存碎片(无法利用的小片内存空间),栈这种数据结构决定了不可能从中间弹出,插入内存块
分配方式 堆动态分配,栈静态和动态分配.其中静态是编译器完成,如局部变量的分配,动态分配,如通过alloca函数分配. 但栈动态分配方式也是结束后自动释放的.
生长方向 堆向上增长(地址增加). 栈向下(地址减小)
效率 栈分配计算机提供了支持,提供专门的压栈弹栈指令,堆分配是c/C++库函数提供,需要找一块合适大小的内存分配,找不到可能要让系统管理内存增加空间.故栈效率高
控制C++的动态内存分配
可以全局重载new ,delete操作符,也可以为为某个类单独重载.
void* operator new(size_t size)
{
cout << "全局分配" << endl;
void *p = malloc(size);
return p;
}
void operator delete(void* p)
{
cout << "全局释放" << endl;
free(p);
}
class A {
public:
//void* operator new(size_t size);
//void operator delete(void* p);
A();
private:
int* num;
};
A::A() {
}
//void* A::operator new(size_t size)
//{
// cout << "分配" << endl;
// return malloc(size);
//}
//
//void A::operator delete(void* p)
//{
// cout << "释放" << endl;
// free(p);
//}
优先调用类重载的,其次调用全局重载的.
常见内存错误及应对
问题1:内存分配失败,却用了它
malloc分配失败,根据返回值p是否为NULL判断是否失败,但new分配失败是抛出异常的, 应该通过try-catch方式
try{
int *p = new int[1000 * size];
return p;
}catch(bad_alloc&e){
throw 1;
}
分配成功了,但没有初始化就引用
int *a = new int[3];
a[0],a[1],a[2]虽然没有越界,但是值却是不确定的.
分配,初始化成功,但访问越界
忘记释放内存
new,delete必须成对,malloc,free同理.
释放了内存却继续使用它
- 如两个指针指向同一块内存,用其中一个指针释放了内存,而通过另一个指针继续访问.
- 函数返回了栈内存的指针(引用)
- delete,free后指针没有置为nullptr,导致野指针.
数组和指针比较
很多地方二者的确可以替换,但二者并不等价. 数组对应的内存要么在栈上,要么对应全局/静态存储区.下面以字符数组和指向字符串的指针为例说明.
区别1:修改内容
char arr[] = "good";
char *ptr = "moring";
arr[0] = 'G';
ptr[0] = 'M'; // error
cout<<arr<<endl;
cout<<ptr<<endl;
VS2019得修改为: const char *ptr = "moring"; 直接编译通不过,g++中可通过,运行出现了段错误.
区别2:复制与比较
数组名不能直接赋值和比较,得用strcpy,strcmp. 指针比较的也是指针变量存储的地址.
区别3:sizeof计算大小
sizeof指针得到的是指针变量的字节数,64位程序8个字节,32位4个字节. 数组用sizeof是可以计算出字节容量. 但数组作为函数参数,会退化为同类型的指针.
指针传递内存
若函数参数是指针,不要指望用它指向开辟的内存从而获取空间.
void GetMemory(char *ptr,int n) {
ptr =(char*) malloc(sizeof(char) * n);
}
int main()
{
char* p = nullptr;
GetMemory(p, 10);
strcpy(p, "good");
cout << p << endl;
return 0;
}
以上是一个经典的例子,而实际上执行的过程, p赋值給ptr, 相当于p和ptr指向了同一个地址,GetMemory中修改了ptr的指向,而p的指向并没有变. GetMemory中申请的内存也无法得到释放.
如果真想申请内存,需要通过ptr改变p的指向,即希望通过ptr修改p存放的地址(不是它的值),那么就需要用到二级指针.或者通过函数返回值(注意不要返回栈空间的地址).
void GetMemory(char **ptr,int n) {
*ptr =(char*) malloc(sizeof(char) * n);
}
char* GetMemory(int n) {
char *ptr =(char*) malloc(sizeof(char) * n);
return ptr;
}
malloc,free细节
void *malloc(size_t size);
void free(void* ptr);
malloc申请失败返回NULL,返回类型需要转换为需要的类型. malloc实际开辟的空间可能大于size.malloc和free的实现原理解析 - JackTang's Blog (jacktang816.github.io)
总之,有专门的数据结构组织实现,数据结构记录了开辟空间的大小.
内存泄露以及检测工具
程序使用malloc,realloc,new等函数分配的内存,使用完后没有手动释放,那么程序中这块内存就不能再用了.
检测方法
截获分配和释放函数.如程序采用malloc分配,free释放,可以对比下分配时的指针是否和释放时的指针对应.
VS2019已经可以可以采用vld帮助监测内存泄露问题了.
#include<iostream>
#include <vld.h>
using namespace std;
void GetMemory(char *p ,int n) {
p =(char*) malloc(sizeof(char) * n);
}
int main()
{
char* p = NULL;
GetMemory(p, 10);
cout << "---------------" << endl;
int *ptr = new int;
system("pause");
return 0;
}