C/C++内存管理:new/delete,operator new/delete用法详解

目录

一.C/C++内存分布

1. 选择题:

2. 填空题:

3.说明

4.堆栈深层理解的考察题目

(1)

(2)

C++内存管理方式——————————————————

二.new/delete操作内置类型

>:1 malloc和new区别

>:2 free和delete区别

举个[]不匹配的例子:

(1)例子1:开链表节点

(2)例子2:栈

(3)例子3:栈构造队列 MyQueue 形式

(4)malloc失败,返回空指针;new失败,抛异常

【2】捕获异常

【3】捕获异常通常写法

5.new的相关易错题目

(1)题目1:求构造函数和析构函数的执行次数

(2)题目2:参数存放在哪个区?new申请的空间存放在哪个区?

(3)题目3:

三.operator new与operator delete函数(重要点进行讲解)

1.operator new 底层是封装了malloc:

operator delete 底层是封装了free:

2.new的真正底层原理(针对自定义类型):

① call operator new :调用operator new函数申请空间 ② call Stack构造函数:在申请的空间上执行构造函数,完成对象的构造

delete的原理

new T[N]的原理

delete[]的原理

3.定位new表达式(placement-new) (了解)

4.malloc/free和new/delete的区别(总结)


C/C++ 内存管理

一.C/C++内存分布

我们先来看下面的一段代码和相关问题
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";
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);
}

1. 选择题:

选项 : A . B . C . 数据段 D . 代码段 C/C++内存管理:new/delete,operator new/delete用法详解_第1张图片

1. globalVar 在哪里?__C__  2.  staticGlobalVar在哪里?__C__
3. staticVar 在哪里?__C__  4.  localVar 在哪里?__A__
5. num1 在哪里?__A__
6. char2 在哪里?__A__  7.  * char2 在哪里?__A_
8. pChar3 在哪里?__A__  9.  * pChar3 在哪里? __D__
10. ptr1 在哪里?__A__  11.  * ptr1 在哪里?__B__

答:(从左往右,从上往下顺序)前3个选C:因为全局变量,静态变量或静态全局变量生命周期都是全局,都属于静态区。静态的全局的在静态区;不可修改的常量,编译好的指令在常量区。const都可以修饰局部,全局,静态变量,不改变其空间位置,只是使其不可修改。常变量也是在栈上的

4,5选A:局部变量和数组都是在栈上。

6,7选A:6是数组存在栈上没问题,7是数组指向的内容,他是把常量字符串的内容拷贝到数组中,数组指向的内容是在栈上

8选A, 9选D:局部指针变量 pChar3在栈上没问题,  pChar3是常量字符串的地址,那么  * pChar3就是常量字符串,常量字符串存在于常量区,所以  * pChar3存在于常量区。
10选A,11选D:ptr1是指针变量,存在于栈上。 * ptr1是指针指向的内容,因为是malloc开辟的,所以 * ptr1存在于堆上。

2. 填空题:

sizeof ( num1 ) = __40__ ;
sizeof ( char2 ) = __5__ ; strlen ( char2 ) = __4__ ;
sizeof ( pChar3 ) = __4/8__ ; strlen ( pChar3 ) = __4__ ;
sizeof ( ptr1 ) = __4/8__ ;
解释: sizeof ( num1 ) 是整个数组大小,10个整形就是40字节。 sizeof ( char2 )算数组大小算上 \0 是5字节,strlen(char2)只算字符串长度不算 \0 是4字节, sizeof ( pChar3 ) 指针大小32位平台是4字节,64位平台是8字节。 strlen ( pChar3 算字符串长度是4字节。 sizeof ( ptr1 )是算指针大小和 sizeof ( pChar3 ) 一样。

3.说明

1. 又叫堆栈,非静态局部变量 / 函数参数 / 返回值等等,栈是向下增长的。
2. 内存映射段 是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共
享内存,做进程间通信。( Linux 课程如果没学到这块,现在只需要了解一下)
3. 用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段 -- 存储全局数据和静态数据。
5. 代码段 -- 可执行的代码 / 只读常量。

4.堆栈深层理解的考察题目

(1)

下面有关c++内存分配堆栈说法错误的是( )

A.对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制

B. 对于栈来讲,生长方向是向下的,也就是向着内存地址减小的方向;对于堆来讲,它的生长方向是向上的,是向着内存地址增加的方向增长

C.对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题

D.一般来讲在 32 位系统下,堆内存可以达到4G的空间,但是对于栈来讲,一般都是有一定的空间大小的

答:

A.栈区主要存在局部变量和函数参数,其空间的管理由编译器自动完成,无需手动控制,堆区是自己申请的空间,在不需  要时需要手动释放

B.栈区先定义的变量放到栈底,地址高,后定义的变量放到栈顶,地址低,因此是向下生长的,堆区则相反

C.频繁的申请空间和释放空间,容易造成内存碎片,甚至内存泄漏,栈区由于是自动管理,不存在此问题

D错。

D.32位系统下,最大的访问内存空间为4G,所以不可能把所有的内存空间当做堆内存使用,故错误

(2)

C++中关于堆和栈的说法,哪个是错误的:( )

A.堆的大小仅受操作系统的限制,栈的大小一般较小

B.在堆上频繁的调用new/delete容易产生内存碎片,栈没有这个问题

C.堆和栈都可以静态分配

D.堆和栈都可以动态分配

答:

A.堆大小受限于操作系统,而栈空间一般有系统直接分配

B.频繁的申请空间和释放空间,容易造成内存碎片,甚至内存泄漏,栈区由于是自动管理,不存在此问题

C.堆无法静态分配,只能动态分配

D.栈可以通过函数_alloca进行动态分配,不过注意,所分配空间不能通过free或delete进行释放

C错。(第一次错选成了D)

C++内存管理方式——————————————————

C语言内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过 new delete 操作符进行动态内存管理

二.new/delete操作内置类型

申请和释放单个元素的空间,使用 new delete 操作符,申请和释放连续的空间,使用 new[]
delete[]

C/C++内存管理:new/delete,operator new/delete用法详解_第2张图片

 new申请对象后用delete释放,并且要格式匹配:int* p1=new int 对应 delete p1;int* p2 = new int[10] 对应 delete[] p2;        对应不匹配,可能会报错,也可能不会报错。建议一定要匹配。

int main()
{
	int* p1 = new int; // new 1个int对象
	int* p2 = new int[10]; // new10个int对象

	// new一个int对象,初始化成10
	int* p3 = new int(10);

	// new10个int对象,初始化成{}中的值
	int* p4 = new int[10]{ 10, 1, 2, 3 };

	// 不匹配,可能会报错,可能不会报错。建议一定要匹配
	delete p1;
	delete[] p2;

	delete p3;
	delete[] p4;
	return 0;
}

>:1 malloc和new区别

 对于内置类型而言,用malloc和new,除了用法不同,没有什么区别                                               
①他们区别在于自定义类型, malloc只开空间,new是 开空间+调用构造函数初始化

②malloc失败,返回空指针;new失败,抛异常(当内存不足时会开辟失败)解释——>(4)

所以malloc必须assert检查,new不用检查。

③new既是操作符也是关键字,malloc是函数

>:2 free和delete区别

free是释放空间,delete是 调用析构函数清理资源+释放空间

举个[]不匹配的例子:

内置类型:

        char* p = new char[100];                                                                                                                delete p;

内存泄漏?报错?崩溃?

虽然是对的,但是不建议这么写,应该写成 delete []p。

内存泄漏吗?:不会,对于内置类型,此时delete就相当于free,因此不会造成内存泄漏

报错吗?:不会,编译不会报错,这个写法虽然不规范但是是对的,建议针对数组释放使用delete[],如果是自定义类型,不使用方括号就会运行时错误

崩溃吗?不会,对于内置类型,程序不会崩溃,但不建议这样使用

自定义类型:

             A* p = new A[100];
             delete p;  (自定义类型不用delete []p;会导致内存泄漏吗?)

不会内存泄漏:new []和new 底层调用operator new,operator new又调用malloc,delete和delete[] 同理,底层都是调用free。所以不会内存泄漏

不会报错,有可能崩溃:崩溃是源自于他底层实现的一些原因,底层实际上就是它为了知道调用多少次析构函数,这里的内存空间前面会给你多开几个字节去存那个对象的个数,像那种方框,delete的时候,他就不会以为你头上有四个字节,这个指针偏移不对,反正等等这些底层原因吧,这个地方可能就会崩溃

(1)例子1:开链表节点

struct ListNode
{
	ListNode* _next;
	int _val;

	ListNode(int val = 0)
		:_next(nullptr)
		, _val(val)
	{}
};

struct ListNode* BuyListNode(int x)
{
	struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));
	assert(node);
	node->_next = NULL;
	node->_val = x;
	return node;
}

int main()
{
	//C语言完整开节点写法:
	//ListNode* n1 = BuyListNode(1);	

    //直接使用malloc开节点:
	ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode)); 
	assert(n1);

    //new写法开节点:
	ListNode* n2 = new ListNode(2);	
	ListNode* n3 = new ListNode(3);
	ListNode* n4 = new ListNode(4);

	return 0;
}

C/C++内存管理:new/delete,operator new/delete用法详解_第3张图片

 new使用起来会很方便!

(2)例子2:栈

class Stack
{
public:
	Stack(int capacity = 0)
	{
		cout << "Stack(int capacity)" << endl;
		_a = new int[capacity];
		_capacity = capacity;
		_top = 0;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_capacity = 0;
		_top = 0;
	}

	void Push(int x)
	{}
private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st;
	Stack* ps1 = (Stack*)malloc(sizeof(Stack));
	//ps1->Stack();  这样是错的,直接malloc以后,就没法初始
	//化了,构造函数不能显示调用,并且成员变量是私有,不能直接访问
	assert(ps1);
	Stack* ps2 = new Stack; // 开空间+调用构造函数初始化

	free(ps1);
	delete ps2; // 调用析构函数清理资源 + 释放空间

	return 0;
}

(3)例子3:栈构造队列 MyQueue 形式

class Stack
{
public:
	Stack(int capacity = 0)
	{
		cout << "Stack(int capacity)" << endl;
		_a = new int[capacity];
		_capacity = capacity;
		_top = 0;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_capacity = 0;
		_top = 0;
	}

	void Push(int x)
	{}
private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue {
public:
//如果Stack没有写默认构造函数,而是普通带参构造函数就需要我们显示的写出来
	//MyQueue()	
	//	: _pushST(4)
	//	, _popST(4)
	//{}
private:
	Stack _pushST;
	Stack _popST;
};

int main()
{
	MyQueue* obj = new MyQueue;   
	delete obj;

	return 0;
}

new一个 MyQueue 空间,调用 MyQueue类 的默认构造函数去初始化成员变量 _pushST 和_popST,因为成员变量是内置类型,MyQueue类 的默认构造函数再去调用对应类的默认构造函数去初始化 _pushST 和_popST 这些成员变量。

(4)malloc失败,返回空指针;new失败,抛异常

【1】(当内存不足时会开辟失败)为了展示malloc和new的报错信息,需要让malloc和new都失败,就先利用p0开一个GB的空间,目的是消耗很大空间,然后再malloc和new就会失败

int main()
{
	void* p0 = malloc(1024 * 1024 * 1024);
	cout << p0 << endl;

	void* p1 = malloc(1024 * 1024 * 1024); //malloc失败,返回空指针
	cout << p1 << endl;

	void* p2 = new char[1024 * 1024 * 1024]; //new失败,抛异常
	cout << p2 << endl;
	return 0;
}

 C/C++内存管理:new/delete,operator new/delete用法详解_第4张图片

【2】捕获异常

为了防止new弹报错窗口,我们需要去捕获异常,这里简单了解一下。

把可能抛异常的部分放在 try 中,当捕捉到异常后,直接跳到catch中,这里就是在p2那一行捕获异常就直接跳到catch中,后面的 cout << p2 << endl;  根本没执行。

int main()
{
	void* p0 = malloc(1024 * 1024 * 1024);
	cout << p0 << endl;

	void* p1 = malloc(1024 * 1024 * 1024);   //malloc失败,返回空指针
	cout << p1 << endl;

	try
	{ 
        //new失败,抛异常
		void* p2 = new char[1024 * 1024 * 1024];
		cout << p2 << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

 C/C++内存管理:new/delete,operator new/delete用法详解_第5张图片

3】捕获异常通常写法

函数封装程序后,把函数放进try中,当捕获到异常就会跳进catch,即:在程序外层捕获异常,不用抛异常后立即捕捉。

void func()
{
	void* p0 = malloc(1024 * 1024 * 1024);
	if (p0 == nullptr)
	{
		cout << p0 << endl;
	}

	// malloc失败,返回空指针
	void* p1 = malloc(1024 * 1024 * 1024);
	if (p1 == nullptr)
	{
		cout << p1 << endl;
	}

	// new失败,抛异常
	void* p2 = new char[1024 * 1024 * 1024];
	cout << p2 << endl;
}

int main()
{
	try
	{
		func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

5.new的相关易错题目

(1)题目1:求构造函数和析构函数的执行次数

【1】

ClassA *pclassa=new ClassA[5];

delete pclassa;

c++语言中,类ClassA的构造函数和析构函数的执行次数分别为( )

A.5,1

B.1,1

C.5,5

D.程序可能崩溃

答:申请对象数组,会调用构造函数5次,delete由于没有使用[],此时只会调用一次析构函数,但往往会引发程序崩溃

选D。

【2】

以下代码中,A 的构造函数和析构函数分别执行了几次: ( )

A*pa=new A[10];

delete []pa;

A.1、1

B.10、10

C.1、10

D.10、1

答:申请数组空间,构造函数调用的次数就是数组的大小,所以调用了10次构造函数;如果释放数组空间,delete使用了[],则会对应的调用数组大小次数的析构函数,调用了10次析构函数。

选B。

(2)题目2:参数存放在哪个区?new申请的空间存放在哪个区?

函数参数使用的空间是在()中申请的,malloc或new是在()中申请空间的?

答案:参数在栈空间存放,malloc或new申请的空间为堆区

栈区 堆区 

(3)题目3:

下面有关malloc和new,说法错误的是? ( )

A.new 是创建一个对象(先分配空间,再调构造函数初始化), malloc分配的是一块内存

B.new 初始化对象,调用对象的构造函数,对应的delete调用相应的析构函数,malloc仅仅分配内存,free仅仅回收内存

C.new和malloc都是保留字,不需要头文件支持

D.new和malloc都可用于申请动态内存,new是一个操作符,malloc是是一个函数

答:

A.new会申请空间,同时调用构造函数初始化对象,malloc只做一件事就是申请空间

B.new/delete与malloc/free最大区别就在于是否会调用构造函数与析构函数

C.需要头文件malloc.h,只是平时这个头文件已经被其他头文件( )所包含了,用的时候很少单独引入,C++中的new是关键字,不需要头文件 故错误

D.new是操作符,malloc是函数

选C。

三.operator newoperator delete函数(重要点进行讲解)

new delete 是用户进行 动态内存申请和释放的操作符 operator new operator delete 是系统提供的 全局函数 new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
operator new与operator delete 和运算符重载没有任何关系,这也是CPP中的一个容易错误理解的误区

1.operator new 底层是封装了malloc:

operator new的源代码:

C/C++内存管理:new/delete,operator new/delete用法详解_第6张图片

operator delete 底层是封装了free:

operator delete的源代码:

C/C++内存管理:new/delete,operator new/delete用法详解_第7张图片

 operator new 跟malloc功能一样, 唯一不同是失败后抛异常(CPP摒弃了C语言中开辟失败返回空指针值的做法,而是转为失败后抛异常)

int main()
{ 
	// 跟malloc功能一样, 唯一不同是失败后抛异常
	Stack* ps2 = (Stack*)operator new(sizeof(Stack));
	operator delete(ps2);

	Stack* ps1 = (Stack*)malloc(sizeof(Stack));
	assert(ps1);
	free(ps1);

	Stack* ps3 = new Stack;
	// call operator new
	// call Stack构造函数

	return 0;
}

2.new的真正底层原理(针对自定义类型):

① call operator new :调用operator new函数申请空间
② call Stack构造函数:在申请的空间上执行构造函数,完成对象的构造

new调用operator new,operator new又调用malloc

C/C++内存管理:new/delete,operator new/delete用法详解_第8张图片

delete的原理

1. 在空间上执行析构函数,完成对象中资源的清理工作
2. 调用operator delete函数释放对象的空间

new T[N]的原理

1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
2. 在申请的空间上执行N次构造函数

delete[]的原理

1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

3.定位new表达式(placement-new) (了解)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用(内存池相当于自己的钱包,堆相当于妈妈的钱包,使用内存池的空间时效率更高)。当你直接new的时候是在堆上开空间,如果你想在内存池上开空间,就需要用到定位new。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
int main()
{
    //定位new写法:相当于先开空间,再调用构造函数初始化
	Stack* obj = (Stack*)operator new(sizeof(Stack));
	 //针对一个空间,显示调用构造函数初始化
	new(obj)Stack(4);

    //等价于new的写法: Stack* obj = new Stack(4)
	return 0;
}

4.malloc/freenew/delete的区别(总结)

malloc/free new/delete 的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
用法上的区别 1~4
1. malloc free 是函数, new delete 是操作符
2. malloc 申请的空间不会初始化, new 可以初始化
3. malloc 申请空间时,需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可
4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要,因为 new 后跟的是空间的类型
底层区别 5~6
5. malloc 申请空间失败时,返回的是 NULL ,因此使用时必须判空, new 不需要,但是 new 需要捕获异常
6. 申请自定义类型对象时, malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new 在申请空间
后会调用构造函数完成对象的初始化, delete 在释放空间前会调用析构函数完成空间中资源的清理

你可能感兴趣的:(C++前期,c语言,c++,开发语言)