一、C/C++内存分布

对于下面这段代码:

int globalVar = 1;
static int staticGlobarVar = 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在哪里?__C__ staticGlobalVar在哪里?__C__
staticVar在哪里?__C__ localVar在哪里?__A__
num1 在哪里?__A__
char2在哪里?__A__ * char2在哪里?__A__
pChar3在哪里?__A__ * pChar3在哪里?__D__
ptr1在哪里?__A__ * ptr1在哪里?__B__

【说明】

1.又叫堆栈--非静态局部变量、函数参数、函数返回值都是存放在栈区的,且栈是向下增长的。

2.内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存做进程间通信。

3.用于存储程序运行过程中动态开辟的空间,堆是向上增长的。

4.数据段-用于存储全局变量可静态数据。

5.代码段-用于存储可执行代码和只读常量。

注:char2和pChar3的详细区别、ptr1的存放方式。*char2是存放在栈里面"abcd"的首个字符'a',*pChar3是存放在常量区里的"abcd"首个字符'a',ptr1是指针存放在栈里面,*ptr1是存放在堆里面动态开辟的空间。

二、C语言中的动态内存管理方式:malloc/calloc/realloc/free

C语言的动态内存函数使用时需要引入头文件

#include<stdlib.h>

malloc、calloc、realloc的区别和free的功能

malloc

void* malloc (size_t size);

向内存申请size个字节大小的连续空间,并返回这块空间的起始地址,不会对空间里面的内容做初始化处理。如果申请失败则返回空指针。

calloc

void* calloc (size_t num, size_t size);

会向内存申请num个size字节大小的连续空间,并返回这块空间的起始地址,并且会将空间的内容初始化为0。如果申请失败则返回空指针。

realloc

void* realloc (void* ptr, size_t size);

对ptr指向的空间进行扩容,扩容后空间的大小为size,并返回新空间的起始地址。如果是异地扩容会将原来空间的内容拷贝到新空间中,并释放原来的空间。如果新空间开辟失败则返回空指针。

free

void free (void* ptr);

释放ptr所指向的空间,如果ptr是空指针则不做处理。无法释放非动态申请空间的指针,会报错。

对于下面这段代码:这里只需要释放p3就行,不再需要对p2进行释放。

int main()
{
	int* p2 = (int*)calloc(4, sizeof(int));
	int* p3 = (int*)realloc(p2, sizeof(int) * 8);

	free(p3);
	return 0;
}

原因:如果是realloc进行的是原地扩容,那么p2和p3指向的都是同一块空间,p2和p3的值是一样的。如果是异地扩容那么realloc再返回新空间的地址给p3后会自动释放原来的空间p2也就不需要再进行释放了。反之如果同时释放了p2和p3则会报错。

三、C++内存管理方式

C++兼容C语言的动态内存管理方式,因此malloc,calloc,realloc和free在C++都能正常使用,并且C语言的动态内存管理方式都能有效的支持内置类型的变量的动态内存申请。但对C语言里面常用的自定义类型变量的动态内存申请非常乏力。因此C++引入了新的内存管理方式new和delete

C++中malloc对自定义类型的乏力区:

C++的类里面包含了6个默认成员函数包括构造函数,析构函数,拷贝构造函数,赋值重载函数,const成员函数,const取地址重载函数,其中构造和析构会在实例化对象或对象的销毁时自动调用。但malloc和free在对自定义类型的内存申请时并不会自动去调用构造和析构。如果是处理例如Stack之类的自定义类型时甚至可能会导致内存泄漏的风险。

所以在C++里面使用malloc和free时还需要手动去写初始化函数,销毁函数或去手动调用构造或析构函数,会很麻烦。

3.1 new/delete操作内置类型

//动态申请1个大小为int类型的空间
int* pa = new int;
//动态申请1个大小为int类型的空间,并初始化空间里的内容为4
int* pa1 = new int(4);
//动态申请4个大小为int类型的空间
int* pa2 = new int[4];
//动态申请4个大小为int类型的空间,并将前三个空间里的内容初始化为1,2,3
int* pa3 = new int[4] {1, 2, 3};

//释放单个空间
delete pa;
delete pa1;
//释放一段连续的空间
delete[] pa2;
delete[] pa3;

对于内置类型来说new的功能和malloc的功能很像,不过可以额外进行初始化。对于申请的单个空间,进行初始化时只需要用 () 存放要初始化的内容即可,对于申请的一块连续的空间初始化时需要用 {} 将要初始化的内容包含起来。

new 类型(初始化内容)、new 类型[]{初始化内容}

【注意】new和delete以及new[]和delete[]要匹配使用。

3.2 new和delete操作自定义类型

对于一个Stack类:

class Stack
{
public:
	Stack(int capacity = 4)
	{
		_top = 0;
		_capacity = capacity;
		_a = new int[_capacity];
	}
	~Stack()
	{
		free(_a);
		_a = nullptr;
		_top = 0;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	//申请一个Stack类的空间
	Stack* pst1 = new Stack;
	Stack* pst2 = new Stack(5);
	//连续申请多个Stack类的空间
	Stack* pst3 = new Stack[2];
	Stack* pst4 = new Stack[3]{ 1,2,3};
	//释放单个空间
	delete pst1;
	delete pst2;
	//释放多个空间
	delete[] pst3;
	delete[] pst4;
	return 0;
}

new和delete对自定义类型的使用方法和堆内置类型的使用方法是一致的。如果不使用()或{}进行初始化时,则会自动调用类的默认构造函数。

动态申请一个对象:new 类名

释放动态申请的一个对象:delete

连续动态申请多个对象:new 类名[数目]

释放多个动态申请的对象:delete[]

在面对自定义类型时,new会做的事情:调用operator new开空间+调用构造函数

在面对自定义类型时,delete会做的事情:调用析构函数+调用operator delete函数释放空间

四、operator new和operator delete函数

new和delete是C++动态内存申请和释放的操作符operator new和operator delete是系统提供的全局函数。在底层汇编代码里面,new会调用operator new全局函数来申请空间delete会调用operator delete全局函数来释放空间

operator new的底层代码:

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

operator delete的底层代码:

/*operator delete: 该函数最终是通过free来释放空间的*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY
		/* get a pointer to memory block header */
		pHead = pHdr(pUserData);
	/* verify block type */
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); /* release other threads */
	__END_TRY_FINALLY
		return;
}
/*free的实现*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

总结:operator new是对malloc的封装,当开空间失败后会抛出异常。operator delete是对free的封装

五、new和delete的实现原理

5.1 内置类型

如果申请的是内置类型的空间,new/delete和malloc/free的功能基本相似,不同的地方是:new和delete是对单个空间的申请和释放,new[]和delete[]是对一段连续空间的申请和释放,且new在申请空间失败时会抛异常,malloc在空间申请失败时会返回空指针

5.2 自定义类型

new的原理

1.调用operator new开辟空间。

2.调用构造函数,实现对对象的初始化。

delete的原理

1.调用析构函数,完成对象资源的清理。

2.调用operator delete释放空间。

new T[N]的原理

1.调用operator new[]函数,在operator new[]中会多次调用operator new函数完成对N个对象空间的申请。

2.在申请的N个空间上调用N次构造函数。

delete[]的原理

1.对申请的N个对象空间调用N次析构函数完成对对象空间内资源的清理。

2.调用operator delete[]函数,在operator delete[]中会多次调用operator delete函数完成对N个对象空间的释放。

【注意】如果new/delete或new T[N]/delete[]不搭配使用的话可能会因为析构的问题报错

六、定位new表达式placement-new

构造函数无法显示调用,析构函数可以显示调用。

Stack* pst = (Stack*)operator new(sizeof(Stack));
//error C7624: 类型名称“Stack”不能出现在类成员访问表达式的右侧
pst->Stack();
pst->~Stack();
operator delete(pst);

定位new可以用于未初始化的对象显示调用构造函数。

使用方式:new(对象指针)构造函数

Stack* pst = (Stack*)operator new(sizeof(Stack));
//定位new
new(pst)Stack(5);

pst->~Stack();
operator delete(pst);

Stack* pst = (Stack*)operator new(sizeof(Stack) * 2);
new(&pst[0])Stack(1);
new(&pst[1])Stack(2);

delete[] pst;

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

malloc/free和new/delete的共同点:都需要从堆上申请空间,都需要手动进行释放。

malloc/free和new/delete的不同点:

1.malloc/free是函数,new/delete是操作符。

2.malloc申请的空间不会初始化,new申请的空间可以进行初始化。

3.malloc申请空间时需要手动进行计算大小,new只需要在[]中传入指定对象的个数就行。

4.malloc的返回值为void*,需要进行强转,new不需要,因为new后面直接接的就是空间的类型。

5.malloc申请空间失败时会返回NULL,且需要进行判断,new申请空间失败时则会抛异常。

6.申请自定义类型对象时,malloc和free只负责申请和释放空间,不会调用构造和析构,new和delete则会在申请和释放空间的基础上调用构造和析构完成对对象的初始化和资源的清理工作。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐