c/c++内存管理

1、 程序内存的分区

  • 存放局部变量、函数参数、局部数组等,生命周期随函数调用结束而销毁(遵循 “先进后出”),栈是向下增长的

  • 存放malloc/calloc/realloc/new动态分配的内存,需要手动free,delete释放,堆是向上增长的

  • 代码段(常量区)存放字符串常量,只读常量(防止意外修改),编译时确定大小

  • 数据段(静态区)存放全局变量、静态变量(static),生命周期为整个程序运行期间

我们来看一个练习题

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 };
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
	free(ptr3);
}

选择题:
A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____
staticGlobalVar在哪里?____
staticVar在哪里?____
localVar在哪里?____
num1 在哪里?____
char2在哪里?____
*char2在哪里?___
pChar3在哪里?____
*pChar3在哪里?____
ptr1在哪里?____
*ptr1在哪里?___

答案CCCAA AAADA B

  • globalVar是全局变量,staticGlobalVar,staticVar是静态变量,三者都储存在数据段

  • localVar是局部变量,num1是局部数组变量,char2是局部字符数组变量,pChar3是局部指针变量,四者都储存在栈

  • char2,内容虽然是字符串“abcd“,但是局部数组会将常量区字符复制到栈上,所以char2在栈上

  • *pChar3,指针指向字符串常量“abcd”,储存在代码段

  • Ptr1局部指针变量,储存在栈上

  • *ptr1指向的动态分配内存(malloc),储存在堆上

2、 C语言中动态内存管理方式,malloc,calloc,realloc,free

#include<iostream>
using namespace std;
int main()
{
	/1、malloc在堆上分配一块指定大小的连续内存空间,不初始化内存
	/malloc返回值为void*,需要强转为int*类型
	/分配能储存10int的空间
	int* p1 = (int*)malloc(10 * sizeof(int));
	
	/检查分配是否成果
	if (p1 == NULL)
	{
		perror("malloc fail");
		return -1;
	}

	/2、calloc与malloc类似,但是将所有字节初始化为0
	/分配5int,初始化为0
	int* p2 =(int*)calloc(5, sizeof(int));
	if (p2 == NULL)
	{
		perror("calloc fail!");
		return -1;
	}

	/3、realloc,扩大缩小已分配内存块的大小
	/内存过大,会把内存分配到新地址
	int* p3 = (int*)realloc(p1, 20 * sizeof(int));
	if (p3 == NULL)
	{
		perror("realloc fail");
		/realloc失败,需要释放原内存,避免内存泄露
		free(p1);
		p1 = NULL;
		return -1;
	}
	
	p1 = p3;/扩容成功后,更新p1指向新地址


	/4、free释放由malloc/calloc/realloc开辟的内存
	int* p = (int *)malloc(sizeof(int));
	free(p);/释放内存
	p = NULL;/避免野指针

//realloc成果后让p1,p3指向同一块空间,因此只需要释放一次
	free(p1);
	free(p2);
	p1 = p2 = p3= NULL;

	return 0;
}

  • 内存泄漏,动态分配的内存没有被释放,导致系统资源耗尽,所以每一个动态开辟的内存都应该free

  • 野指针,释放内存后没有置为空指针,后续误用会导致程序崩溃,所有free后立即赋值NULL

3、 C++的内存管理方式new/delete

#include<iostream>
using namespace std;
int main()
{
	/动态申请一个int类型空间
	int* p1 = new int;
	
	/动态申请一个int类型的空间并初始化为10
	int* p2 = new int(10);

	/动态申请10int类型的空间
	int* p3 = new int[10];
	
	/如果要初始化数组,可以使用列表初始化(C++11/ 剩余元素自动补0
	/int* p3 = new int[10] {0, 1, 2, 3, 4, 5, 6,};


	/new对应deletenew[]对应delete[],申请了多少内存就释放掉多少
	delete p1;
	p1 = nullptr;
	delete p2;
	p2 = nullptr;
	delete[] p3;
	p3 = nullptr;

	return 0;
}

  • New/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new申请空间失败时会抛异常(这个我们以后再说),malloc会返回NULL
  • New和delete是C++的专属内存管理运算符,与C的malloc/dree相比,最大的特点是会自动调用对象的构造函数和析构函数
    在这里插入图片描述

4、 定位new

  • 定位new是一种特殊的new运算符用法,它允许在已分配的内存空间上调用构造函数初始化对象,而不是重新向系统申请新的内存
  • 格式:new (指针) 类型(构造函数参数); // 在指针指向的内存上构造对象
  • 使用场景,定位new表达式在实际中一般是配合内存池使用,因为内存池分配出的内存没有初始化,如果是自定义类型的对象,需要使用new的定义表达式经行显式调构造函数进行初始化
#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	int _a;
};
int main()
{
	/p1现在指向的是与A对象相应大小的一块空间,还不能算是一个对象
	/因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));

	/定位new,在p1指向的内存上调用A的构造函数(真正的创造对象)
	new(p1)A;
	/需要手动调用析构函数
	p1->~A();
	free(p1);

	/调用operator new函数,只分配空间,不调用构造
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

5、 operator new与operator delete函数

  • operator new/operator delete 是底层内存管理函数,负责分配/释放原始内存,不涉及对象构造/析构。相当于C的malloc/free
  • operator new分配内存失败会抛出std::bad_alloc 异常new操作符符就是调用operator new来申请空间,再调用构造函数来实例化对象
  • delete操作符符就是调用析构函数,再调用operator delete来释放内存空间
  • new[]会调用operator new[]分配数组内存,再调用每个元素的构造函数,delete[]先调用每个元素的析构函数,再调用operator delete[]释放内存

6、 malloc/free和new/delete的区别

  • malloc和free是函数,new和delete是操作符
  • malloc和free是负责内存分配和释放,new和delete不仅分配释放内存,还会自动调用构造函数和析构函数
  • malloc申请空间时需要手动计算空间大小并传递,new只需在后边跟上空间类型即可,如果时多个对象,[]中指定对象个数即可
  • malloc返回值为void*,需要手动强制转换为目标类型,new直接返回目标类型指针(如int *)不需要转换
  • malloc内存分配失败时返回NULL指针,所有使用必须判空。new失败时会抛异常,要捕获异常
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐