C++ 内存管理

631 阅读3分钟

内存管理

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同理.

释放了内存却继续使用它

  1. 如两个指针指向同一块内存,用其中一个指针释放了内存,而通过另一个指针继续访问.
  2. 函数返回了栈内存的指针(引用)

image.png

  1. 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;
}