C++语言基础——C/C++内存管理

本文介绍了C++中的动态内存管理方式,包括new/delete操作符的使用、特点及其实现原理,并对比了与C语言中malloc/free的区别。此外还讨论了单例模式的实现以及内存泄漏的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1. 虚拟地址空间

2. 动态内存管理方式

3. C++中动态内存管理

3.1 概述

3.2 new/delete操作

3.3 operator new与operator delete函数

3.4 new和delete的特点

3.5 new和delete的实现原理

3.6 new/delete 与 malloc/free的区别

3.7 定位new表达式(placement-new)

4. 单例模式

4.1 概述

4.2 饿汉

4.3 懒汉

5. 内存泄漏

5.1 概述

5.2 分类

6. 简单理解new和delete

1. 虚拟地址空间

先来回顾一下程序地址空间吧。

2. 动态内存管理方式

在之前学习C语言的时候,我们动态内存管理比较常用的就是 malloc了,当然我们也用calloc,realloc。但是不常用这两个,这三个函数申请内存都是在堆上申请的,毕竟堆大啊。这三个函数在前面的博文有详细的介绍,请点击进入。为了方便下面概念的对比,下面来回顾一下

void CMemory() {
	// malloc(size_t ize)
	// 在堆上分配一块长度为size字节的连续区域。
	// 参数size为需要的内存空间的长度,返回该区域的首地址
	int* p1 = (int*)malloc(sizeof(int));

	// calloc(size_t count, size_t size)
	// 在堆上分配count个size字节的连续区域
	// 并所分配的空间中的每一位都初始化为零
	int *p2 = (int*)calloc(1, sizeof(int));

	// realloc(void *ptr, size_t size);
	// 给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地
	int* p3 = (int*)realloc(p2, 3);
}

3. C++中动态内存管理

3.1 概述

C++语言是C语言的的超集,因此也能直接使用上面申请内存的函数。但是C++提供了能够抛出异常的关键字来申请和释放内存的关键字——new和delete。new是申请内存,delete是释放内存。这里说明两个类型——基础类型和自定义类型
基础类型:编程语言定义的基础类型,如:int,char等
自定义类型:我们定义的类,如:结构体,类等

3.2 new/delete操作

先简单使用一下new和delete。

// 基础类型
void CppMemory() {
	int* p1 = new int;

	int * p2 = new int(10); // *p2 = 10

	int* p3 = new int[3]; // 申请了连续的三个int长度的空间
}

// 自定义类型
class MemoryTest {
public:
	MemoryTest()
		:_data(0)
	{}
private:
	int _data;
};
void Test() {
	MemoryTest* t2 = new MemoryTest;
	delete t2;

	MemoryTest* t3= new MemoryTest[3];
	delete [] t3;
}

3.3 operator new与operator delete函数

operator new 和operator delete是系统提供的全局函数。不是new和delete的重载。
operator new 是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。

3.4 new和delete的特点

  • new的特点

1)调用operator new函数申请空间
2)在申请的空间上执行构造函数,完成对象的构造

  • new T[N]的特点

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

  • delete的特点

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

  • delete[]的特点

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

可以看到在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。

3.5 new和delete的实现原理

图中的内置类型就是基础类型,由于是基础类型,因此是没有构造的。但是我们自定义的类,是由构造函数的,在创建对象时就会调用我们的构造函数完成对象的初始化工作。

3.6 new/delete 与 malloc/free的区别

new/deletemalloc/free
不同点new和delete是操作符malloc和free是函数
new可以初始化malloc申请的空间不会初始化
new只需在其后跟上空间的类型即可malloc申请空间时,需要手动计算空间大小并传递
new后跟的是空间的类型malloc的返回值为void*, 在使用时必须强转
new申请空间失败时,需要捕获异常malloc申请空间失败时,返回的是NULL
new在申请空间后会调用构造函数完成对象的初化,delete在释放空间前会调用析构函数完成空间中资源的清理申请自定义类型对象时,malloc/free只会开辟空间
相同点malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。

3.7 定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表

class NewPress {
public:
	NewPress()
		:data_(0) {
		cout << "定位new表达式" << endl;
	}
private:
	int data_;
};
int main() {
	//定位new表达式
	NewPress* test = (NewPress*)malloc(sizeof(NewPress));

	new(test) NewPress;

	free(test);

	system("pause");
	return 0;
}

4. 单例模式

4.1 概述

    一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。单例共有两种实现方法——饿汉和懒汉。

4.2 饿汉

在程序的一开始便创建一个唯一的对象。
实现:构造函数和拷贝构造函数私有
(1)定义一个静态单例成员,静态成员在程序运行程序之前完成初始化
(2)提供一个静态方法获取单例静态成员

// 饿汉方法
// 优点:简单
// 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
class Singleton {
	static Singleton& getObj() { // 设置静态的获取对象的方法
		return data_;
	}
private:
	static Singleton data_; //创建一个静态的对象
	Singleton() {}; // 私有化构造函数
	
	Singleton(const Singleton &) = delete; // 拷贝构造删除,避免拷贝对象
};
Singleton Singleton::data_ ; // 程序一开始就初始化

4.3 懒汉

在用到该对象的时候,才创建该对象
实现:构造函数和拷贝构造函数私有
(1)定义一个静态单例成员指针,静态成员指针在程序运行程序之前完成初始化
(2) 提供一个静态方法创造对象,返回指针。

// 懒汉方法:
// 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
// 缺点:复杂
#include<mutex>//提供锁是为了,在一个线程创造对象时,如果创造对象的过程较长防止其他对象也创造对象。
class Singleton2 {
public:
	static Singleton2* getObj() {
		if (obj2_ = nullptr) { // 两次检查,提高效率
			mtx_.lock(); // 保证对象只创建一次。即使有线程进来,也会被锁在外面
			if (obj2_ == nullptr) {
				obj2_ = new Singleton2;
			}
			mtx_.unlock();
		}
		return obj2_;
	}
	class Gc { // 可有可无,程序结束,进程释放资源
	public:
		~Gc() { // 定义一个内部类的析构,是因为不能在本类中调用自身的析构,否则会发生递归调用。
			if (obj2_) {
				delete obj2_;
				obj2_ = nullptr;
			}
		}
	};
private:
	Singleton2() {}
	Singleton2(const Singleton&) = delete;
	static Singleton2* _obj2; // 指针只占四个字节,没有对象,当需要的时候才创建
	static mutex _;mtx
};

Singleton2* Singleton2::obj2_ = nullptr;
mutex Singleton2::mtx_;

 

5. 内存泄漏

5.1 概述

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

5.2 分类

(1)堆内存泄漏(Heap leak):程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
(2)系统资源泄漏:程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

6. 简单理解new和delete

 (1)创建一个类,该类只能在堆上创建

// 1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
// 2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
class HeapObj {
public:
	// 定义成静态的,可用类名经行调用
	static HeapObj getObj() {
		HeapObj* obj = new HeapObj; // 在堆上进行申请内存
		return *obj;
	}
private:
	HeapObj();
	HeapObj(const HeapObj& d);
	int _data;
};

(2)创建一个类,该类只能在栈上创建 

// 1. 将类的构造函数私有。。
// 2. 拷贝构造不用,因为拷贝构造是根据已经有的对象在栈生成对象
// 3. 提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建
class StackObj {
	static StackObj getObj() {
		return StackObj();
	}
private:
	StackObj()
		:_data(0) {

	}
	// 删除 operator new(size_t size)是其不能在堆上创建
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
private:
	int _data;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值