C++初阶:C/C++内存管理、new与delete详解

之前结束了类与对象:今天进行下面部分内容的学习


文章目录

  • 1.C/C++内存分布
  • 2.C语言中动态内存管理方式:malloc/calloc/realloc/free
  • 3.C++动态内存管理方式
    • 3.1new/delete操作内置类型
    • 3.2new和delete操作自定义类型
  • 4.operator new与operator delete函数
  • 5.new和delete的实现原理
    • 5.1内置类型
    • 5.2自定义类型
  • 6.定位new表达式(placement-new)
  • 7.知识点梳理
    • malloc/free和new/delete的区别


1.C/C++内存分布

请添加图片描述

具体说明:

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。栈上的内存分配和释放是通过编译器生成的代码来管理的,通常是通过在函数退出时进行清理来实现的
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。堆上的内存通常需要手动管理,需要显式地分配和释放堆上的内存
  4. 数据段–存储全局数据和静态数据。全局数据和静态数据的销毁通常是在程序结束时由操作系统自动完成,二者的生命周期也是整个程序
  5. 代码段–可执行的代码/只读常量

下面根据具体代码来看:

int globalVar = 1;//全局变量,在静态区
static int staticGlobalVar = 1;//全局静态变量,在静态区

int main()
{
	static int staticVar = 1;//局部静态变量,在静态区。作用域只在这个函数内,但是生命周期是在整个程序
	int localVar = 1;//局部变量,在栈
	int num1[10] = { 1, 2, 3, 4 };//局部变量,在栈

	char char2[] = "abcd"; //局部变量,在栈。
	//其实[]这个符号:是把位于常量区的"abcd",拷贝到栈上,再去指向

	const char* pChar3 = "abcd";//pChar3在栈上,但是是直接指向常量区的"abcd"。所以*pChar3在常量区

	int* ptr1 = (int*)malloc(sizeof(int) * 4);//ptr1这个指针变量还是在栈,但是*ptr1是在堆上
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
	free(ptr3);

	return 0;
}

那全局变量和全局静态变量的区别是??

全局变量具有外部链接性,可以被其他文件中的函数访问。
静态全局变量具有内部链接性,只能被声明它的文件内的函数访问(本文件)


2.C语言中动态内存管理方式:malloc/calloc/realloc/free

  1. malloc()函数:
    • 功能:malloc函数用于在堆上动态分配指定大小的内存空间。
    • 语法:void* malloc(size_t size);
    • 返回值:如果分配成功,则返回指向分配内存的指针;如果分配失败,则返回NULL
  2. calloc()函数:
    • 功能:calloc函数用于在堆上动态分配指定数量、指定大小的内存空间,并将分配的内存空间初始化为0。
    • 语法:void* calloc(size_t num, size_t size);
    • 返回值:如果分配成功,则返回指向分配内存的指针;如果分配失败,则返回NULL
  3. realloc()函数:
    • 功能:realloc函数用于更改之前分配的内存块的大小,可以扩大或缩小内存块的大小。
    • 语法:void* realloc(void* ptr, size_t size);
    • 返回值:如果分配成功,则返回指向重新分配内存的指针;如果分配失败,则返回NULL。如果返回的指针与之前的指针不同,意味着内存块的大小或位置可能已经改变了。
  4. free()函数:
    • 功能:free函数用于释放之前动态分配的内存空间,将其返回给系统供其他程序使用。
    • 语法:void free(void* ptr);
    • 返回值:无。

异同点:

  • malloccalloc都用于分配内存,但calloc在分配内存后会将其初始化为0,而malloc不会。
  • realloc用于更改之前分配的内存块的大小,可以扩大或缩小内存块的大小,而malloccalloc只能用于分配新的内存块。
  • free用于释放动态分配的内存,将其返回给系统供其他程序使用

更加详细的介绍大家可以移步于我的文章:


3.C++动态内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力(特别是关于自定义变量),因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理

在C++中,newdelete是用于动态内存管理的运算符,它们提供了对malloccallocreallocfree等C语言内存管理函数的更高级的封装和功能。

  1. new运算符:
    • 功能:new运算符用于在堆上动态分配内存,并调用对象的构造函数来初始化这块内存。
    • 语法:new 类型new 类型[大小],例如:new intnew int[10]
    • 返回值:如果分配成功,则返回指向分配内存的指针;如果分配失败,则抛出std::bad_alloc异常。
  2. delete运算符:
    • 功能:delete运算符用于释放由new分配的内存,并调用对象的析构函数来销毁对象。
    • 语法:delete 指针delete[] 指针,例如:delete ptrdelete[] arr
    • 返回值:无。

与C语言中的mallocfree相比,newdelete的优势在于:

  • newdelete运算符,而不是函数,因此它们可以重载,从而实现自定义的内存分配和释放策略。
  • newdelete调用对象的构造函数和析构函数,从而确保对象的正确初始化和清理。
  • newdelete支持运算符重载,可以用于自定义类的动态内存管理,而mallocfree只能用于分配和释放原始内存块

3.1new/delete操作内置类型

直接上代码:

int main()
{
	// 动态申请一个int类型的空间
	int* a = new int;

	// 动态申请一个int类型的空间并初始化为11
	int* b = new int(11);

	// 动态申请3个int类型的空间
	int* c = new int[3];

	// 动态申请10个int类型的空间,并进行部分初始化
	int* d = new int[10]{ 1,2,3 };


	delete a;
	delete b;
	delete[] c;
	delete[] d;
	return 0;
}

请添加图片描述

内置类型的对象申请释放,new和malloc除了用法上,没有区别

但是malloc不方便解决动态申请的自定义类型对象的初始化问题,不会调用构造函数

3.2new和delete操作自定义类型

class A
{
public:
	A(int a=0)
		:_a(a)
	{}
	~A()
	{
		cout << "调用了~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	//动态申请一个A类型的空间
	A* pa1 = new A(1);

	//动态申请3个A类型的空间
	A a1;
	A a2;
	A a3;
	A* pa2 = new A[3]{ a1, a2, a3 };
	//可以直接写成以下两种
	A* pa3 = new A[3]{ A(1), A(2), A(3) };//匿名对象
	//再少点
	A* pa4 = new A[3]{ 1,2 ,3 };//隐式类型转换,有单形参的构造函数支持

	delete pa1;
	delete[] pa2;
	delete[] pa3;
	return 0;
}

new的本质:开空间+调用构造函数初始化

delete的本质:析构函数+释放空间


4.operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间

大家注意啊,这两个函数不是重载,只是祖师爷把他们命名为这个,他们是全局函数。

  1. operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
  2. operator delete: 该函数最终是通过free来释放空间的
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{

			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK);
	__TRY
		pHead = pHdr(pUserData);
	_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
	_free_dbg(pUserData, pHead->nBlockUse);
	__FINALLY
		_munlock(_HEAP_LOCK); 
	__END_TRY_FINALLY
		return;
}

看到:其实operator new 实际也是通过malloc来申请空间。operator delete 最终是通过free来释放空间的

二者其实就是对malloc和free进行了封装,加上了抛出异常的修饰


5.new和delete的实现原理

5.1内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:

new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常malloc会返回NULL

5.2自定义类型

  • new的原理
  1. 调用operator new(跟malloc效果一样)函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造
  • delete的原理
  1. 在空间上执行析构函数,完成对象中资源的清理工作(先调用析构函数,释放成员变量申请的空间,不然会找不到)
  2. 调用operator delete函数释放对象的空间
  • new TYPENAME[N]的原理
  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请(一次性申请)
  2. 在申请的空间上执行N次构造函数
  • delete[]的原理
  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

但是要注意一件事:

class Stack
{
public:
	Stack(int capacity = 3)
	{
		cout << "调用了构造函数" << endl;
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete _a;
		_a = nullptr;
		_top = -1;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack* a1 = new Stack[10];
	cout << sizeof(*a1);
	delete[] a1;
	return 0;
}

实际上开了124个字节,大家回想怎么会多4个呢?不是 10 ∗ 12 10*12 1012吗?

那四个字节来储蓄申请了几个对象,这里就是10

请添加图片描述


6.定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式:

new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

class Stack
{
public:
	Stack(int capacity = 3)
	{
		cout << "调用了构造函数" << endl;
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete _a;
		_a = nullptr;
		_top = -1;
		_capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack* s1 = (Stack*)operator new(sizeof(Stack));
	//都知道构造函数不能显示调用
	//s1->Stack();

	//但是可以使用定位new显示调用构造函数
	new(s1)Stack(2);

	//但是析构函数可以
	s1->~Stack();
	operator delete (s1);
	return 0;
}

7.知识点梳理

malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地

方是:

  1. mallocfree函数newdelete操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,在[]中指定对象个数即可
  4. malloc返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化delete在释放空间前会调用析构函数完成空间中资源的清理

前5条是用法上面的区别

最后一条是原理上的区别


这次内容就到这里啦,下次会带来模版相关的知识,感谢大家支持!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/355947.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

题目:冒险者公会(蓝桥OJ 3611)

问题描述&#xff1a; 解题思路&#xff1a; 官方&#xff1a; 注意点&#xff1a; 在前期的排序操作&#xff0c;因为需要找到如样例所示的每轮最大&#xff0c;因此我们需要用0代替没有的委托&#xff08;即样例斜杠&#xff09;。如何用0代替&#xff1a;将村庄委托数量默认…

基于springboot校园交友网站源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

ArcgisForJs快速入门

文章目录 0.引言1.前端代码编辑工具2.使用ArcgisForJs创建一个简单应用3.切片地图服务图层4.动态地图服务图层5.地图事件 0.引言 ArcGIS API for JavaScript是一款由Esri公司开发的用于创建WebGIS应用的JavaScript库。它允许开发者通过调用ArcGIS Server的REST API&#xff0c…

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现

C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现 —— 杭州 2024-01-28 code review! 文章目录 C++笔记之RTTI、RAII、MVC、MVVM、SOLID在C++中的表现1.RTTI、RAII、MVC、MVVM、SOLID简述2.RAII (Resource Acquisition Is Initialization)3.RTTI (Run-Time Type Informat…

ABeam Insight | 大语言模型系列 (1) : 大语言模型概览

大语言模型系列 引入篇 ABeam Insight 自从图灵测试在20世纪50年代提出以来&#xff0c;人类一直不断探索机器如何掌握语言智能。语言本质上是一个由语法规则支配的错综复杂的人类表达系统。 近年来&#xff0c;具备与人对话互动、回答问题、协助创作等能力的ChatGPT等大语…

江科大stm32学习笔记6——GPIO输入准备

一、按键消抖 由于按键内部使用的是机械式弹簧片&#xff0c;所以在按下和松开时会产生5~10ms的抖动&#xff0c;需要通过代码来进行消抖。 二、滤波电容 在电路中&#xff0c;如果见到一端接在电路中&#xff0c;一端接地的电容&#xff0c;则可以考虑它的作用为滤波电容&am…

python爬虫实战——获取酷我音乐数据

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 开发环境: 版 本&#xff1a; python 3.8 编辑器&#xff1a;pycharm 2022.3.2 模块使用: requests >>> pip install requests 如何安装python第三方模块: win R 输入 cmd 点击确定, 输入安装命令 pip install…

职业身份来认同自己对吗?

人们常常以自己的职业身份来认同自己。这是一个巨大的错误。 你的职业身份只是一个外壳&#xff1b;它不能定义你是一个人。你可以期待你的职业媒介会随着时间而改变&#xff0c;但是你传达的信息会应该更加稳定。你的信息就是回答你是谁&#xff0c;你应该通过三十几年的工作…

linux中常用的命令

一&#xff1a;tree命令 &#xff08;码字不易&#xff0c;关注一下吧&#xff0c;w~~w) 以树状形式查看指定目录内容。 tree --树状显示当前目录下的文件信息。 tree 目录 --树状显示指定目录下的文件信息。 注意&#xff1a; tree只能查看目录内容&#xff0c;不能…

【Axure高保真原型】随机抽取案例

今天和大家分享随机抽取点餐案例的原型模板&#xff0c;包括2种效果&#xff0c;第一种是手动暂停效果&#xff0c;点击开始后随机抽取食物&#xff0c;手动点击暂停按钮后停止&#xff1b;第二种是自动暂停效果&#xff0c;点击开始按钮后随机抽取食物&#xff0c;并且开始倒计…

webassembly003 whisper.cpp的python绑定实现+Cython+Setuptools的GUI程序

CODE python端的绑定和本文一样&#xff0c;还需要将cdef char* LANGUAGE b’en’改为中文zh&#xff08;也可以在函数中配置一个参数修改这个值&#xff09;。ps:本来想尝试cdef whisper_context* whisper_init_from_file_with_params_no_state(char*, whisper_full_params)…

Gitlab7.14 中文版安装教程

Gitlab7.14 中文版安装教程 注&#xff1a; 本教程由羞涩梦整理同步发布&#xff0c;本人技术分享站点&#xff1a;blog.hukanfa.com转发本文请备注原文链接&#xff0c;本文内容整理日期&#xff1a;2024-01-28csdn 博客名称&#xff1a;五维空间-影子&#xff0c;欢迎关注 …

部署LNMP、Nginx+FastCGI、Nginx地址重写语法,地址重写应用案例

1 案例1&#xff1a;部署LNMP环境 1.1 问题 安装部署LNMP环境实现动态网站解析 静态网站 在不同环境下访问&#xff0c;网站内容不会变化 动态网站 在不同环境下访问&#xff0c;网站内容有可能发生变化 安装部署Nginx、MariaDB、PHP、PHP-FPM&#xff1b;启动Nginx、Mari…

STM32标准库——(6)TIM定时中断

1.TIM简介 TIM&#xff08;Timer&#xff09;定时器定时器可以对输入的时钟进行计数&#xff0c;并在计数值达到设定值时触发中断16位计数器、预分频器、自动重装寄存器的时基单元&#xff0c;在72MHz计数时钟下可以实现最大59.65s的定时不仅具备基本的定时中断功能&#xff0…

LVGL部件

一.标签部件 1.如何创建标签部件以及设置文本 ![2024-01-28T09:54:08.png][3] void my_lvgl(void) {lv_obj_t *lablelv_label_create(lv_scr_act()); //创建一个标签lv_label_set_text(lable,"hello"); //普通更改文字lv_label_set_text_fmt(lab…

Zerosync:构建基于STARK的Bitcoin证明系统

1. 引言 前序博客&#xff1a; BitcoinSTARK: ZeroSync & Khepri Robin Linus、Tino Steffens、Lukas George 等人成立了一个名为 ZeroSync 协会&#xff08;ZeroSync Association&#xff09;的瑞士非营利组织&#xff0c;该组织将牵头开发比特币证明系统。ZeroSync 于…

shell

目录 一.运行方式 二.编程习惯 三.变量 3.1变量的命名 3.3普通变量(局部变量) 3.4特殊变量 3.5变量子串 3.6变量赋值 四.运算方式 4.1$(( )) 4.2let 4.3expr 4.4bc(小数运算) 4.5$[ ] 4.6awk 4.7总结运算方式 五.条件测试语句 5.1文件 5.2条件测试表达式…

js实现动漫拼图1.0版

文章目录 1 实现效果视频2 功能实现思路3代码实现 1 实现效果视频 拼图1.0 2 功能实现思路 布局忽略&#xff08;小白学前端&#xff0c;不献丑了&#xff09; 左侧拼图格 左侧4*4的拼图小格子 利用表格实现&#xff0c;规划好td的大小&#xff0c;给每个格子加上背景图片&…

计算方法实验2:利用二分法及不动点迭代求解非线性方程

一、问题描述 利用二分法及不动点迭代求解非线性方程。 二、实验目的 掌握二分法及不动点迭代的算法原理&#xff1b;能分析两种方法的收敛性&#xff1b;能熟练编写代码实现利用二分法及不动点迭代来求解非线性方程。 三、实验内容及要求 二分法 (1) 编写代码计算下列数字…

类和对象 第五部分第四小节:赋值运算符重载

C编译器至少给一个类添加4个函数 1.默认构造函数无参&#xff0c;函数体为空 2.默认析构函数无参&#xff0c;函数体为空 3.默认拷贝沟早函数&#xff0c;对属性进行值拷贝 4.赋值运算符“operator”&#xff0c;对属性进行值拷贝 如果类中有属性指向堆区&#xff0c;做赋值操作…