C/C++内存管理(超详解)

目录

1.C/C++内存分布

2.C语言动态内存管理

[2.1 malloc](#2.1 malloc)

[2.2 free](#2.2 free)

[2.3 calloc](#2.3 calloc)

[2.4 realloc](#2.4 realloc)

3.C++动态内存管理

3.1new/delete操作内置类型

3.2new/delete操作自定义类型

[3.3operator new与operator delete函数](#3.3operator new与operator delete函数)

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


1.C/C++内存分布

**内存中是如何布局的?**如下图,,一般内存主要分为:代码区、常量区、静态区(全局区)、堆区、栈区这几个区域。

内存分布说明:

  1. 内核空间: 放置操作系统相关的代码和数据。(用户不能直接进行操作 ------ 可以通过调用系统提供的 api 函数)一般是给操作系统预留的内存空间,系统会进行相应的保护。
  2. 栈又叫堆栈:非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  3. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  4. 堆用于程序运行时动态内存分配,堆是向上增长的。
  5. 数据段:存储全局数据和静态数据。(如static修饰的变量或在函数外定义的全局变量)
  6. 代码段:可执行的代码/只读常量。****(如常量字符串)

2.C****语言动态内存管理

2.1 malloc

函数信息:

上代码:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	//开辟10个整型的空间:
	
	int* p = (int*)void* p = malloc(10 * sizeof(int));

	//1.开辟失败
	if(p == NULL)
	{
		perror("main");
		return 0;
	}
	//2.开辟成功
	int i = 0;
	for(i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//回收空间
	free(p);
	p = NULL;
	return 0;
}

小结:

  1. malloc向堆区申请一块连续的空间,开辟成功返回那块空间的地址,开辟失败返回NULL
  2. 因为malloc函数的返回值是void*类型,在用指针变量接收时,必须强制类型转换为对应类型
  3. 如果malloc开辟失败,可能会对空指针进行非法解引用操作,malloc开辟的空间一定要检查
  4. 使用完空间,要主动回收。因为在回收空间后,那块空间的使用权交给了操作系统,为了避免非法访问,通常会主动将指向那块空间的指针置为NULL

2.2 free

函数信息:

上代码:

cpp 复制代码
#include<stdio.h>
int main()
{
	int a = 1;
	int* b = &a;
	free(b);//回收空间
	b = NULL;
	return 0;
}

小结:

  1. 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是标准未定义的
  2. 如果参数ptr是NULL指针,则视为无效代码,因为不能对一个不存在的地址释放空间,这样没有意义!
  3. 关于空间回收:可以是主函数结束后,所有开辟的栈帧会被还给操作系统,也可以是主动把不用的空间主动回收掉。

2.3 calloc

函数信息:

上代码:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	//malloc与calloc进行对比
	//malloc
	int* p = (int*)malloc(40);
	int i = 0;
	for(i = 0; i < 10; i++)
	{
		printf("%d\n", *(p + i));
	}
	free(p);
	p = NULL;
	
	//calloc
	int* q = (int*)calloc(10, sizeof(int));
	for(i = 0; i < 10; i++)
	{
		printf("%d\n", *(q + i));
	}
	free(q);
	q = NULL;
	return 0;
}

小结:

  1. 相比于malloc来说,calloc与malloc唯一的不同就是calloc会进行初始化,并且要指定开辟空间的单个大小及个数。

2.4 realloc

函数信息:

上代码:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
int main()
{
	//开辟10个整形大小
	int* p = (int*)calloc(10, sizeof(int));
	if(p == NULL)
	{
		perror("main");
		return 0;
	}
	//如果空间不够,还需要10个整型空间,使用realloc调整空间	
	int* pnew = (int*)realloc(p, 20 * sizeof(int));
	if(pnew != NULL)
	{
		p = pnew;	
	}
	//回收空间
	free(p);
	p = NULL;
	return 0;
}

小结:

realloc开辟空间原理:

  1. realloc在调整空间时,发现后面有足够的空间能调整,则直接紧挨着原空间之后进行调整,并且返回原空间的地址;
  2. realloc在调整空间时,发现后面没有足够的空间能调整,则会找一块能容纳原空间和需要增加的增加空间的新空间,并把原空间的内容全部拷贝到新空间,同时把原空间主动释放掉,最后在返回新空间的地址。

3.C++动态内存管理

3.1new/delete****操作内置类型

关于new和delete我们通过代码来介绍:

cpp 复制代码
int main()
{
	// 动态申请一个int类型的空间
	int* ptr4 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* ptr5 = new int(10);
	// 动态申请10个int类型的空间
	int* ptr6 = new int[10];
	delete ptr4;
	delete ptr5;
	delete[] ptr6;
	return 0;
}

讲解
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,将new[]和delete[]匹配起来使用。如果要进行初始化的话,对于内置类型单个变量使用(初始值),如果是数组则在[]后加上{初始值1,初始值2,初始值3......};

3.2****new/delete操作自定义类型

上代码:

cpp 复制代码
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	A* p2 = new A(1);
	free(p1);
	delete p2;//这里将delete和C语言中的free混用对于自定义类型来说看不出区别
	          //但对于内置类型有很大概率会出bug,不是好的代码习惯!

	int* p3 = (int*)malloc(sizeof(int)); 
	int* p4 = new int;
	free(p3);
	delete p4;//这样才是正确回收空间操作,malloc匹配free,new匹配delete
	A* p5 = (A*)malloc(sizeof(A) * 10);
	A* p6 = new A[10];//这里可以对A数组进行初始化,可以直接给值,也可以用匿名对象构造初始化
	//A* p7 = new A[10]{ 1,2,3,4,5,6,7,8,9,10 };
	//A* p8 = new A[10]{ A(1),A(2),A(3),A(4),A(5),A(6),A(7),A(8),A(9),A(10) };
	free(p5);
	delete[] p6;
	return 0;
}

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会,这是C++动态操作与C语言动态操作的区别之一,为什么会调用析构和构造函数,请继续往下看:

先来介绍一下operator new和operator delete:

3.3operator newoperator delete函数

先弄清几个点:

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

纸上得来终觉浅,我们扒开两个函数的底层实现看看:

cpp 复制代码
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
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: 该函数最终是通过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)

小结:

  1. operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。如何查看异常呢,使用try+catch来捕捉:

    cpp 复制代码
    #include"iostream"
    using namespace std;
    class A
    {
    public:
    	A(int year=2025,int month=1,int day=19)
    	{
    		cout << year << "-" << month << "-" << day << endl;
    		this->_year = year;
    		this->_month = month;
    		this->_day = day;
    	}
    	~A()
    	{
    		cout << "Destroy" << endl;
    		_year = _month = _day = 0;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	try
    	{
    		A* tmp = new A[10]{ A(2025,1,10),A(2025,1,11) };//先operator new->malloc再构造
    		delete[] tmp;//先析构再operator free->free
    	}//malloc失败返回空指针 new失败抛异常捕捉
    	catch (const exception& e)
    	{
    		cout << e.what() << endl;
    	}
    
    	return 0;
    }
  2. operator delete 最终是通过free来释放空间的

  3. new操作符首先调用operator new全局函数通过底层malloc开辟一块空间,再调用构造函数对自定义类型初始化。

  4. delete操作符首先调用析构函数销毁自定义类型占用的空间,再调用operator delete全局函数通过底层free释放空间。

3.4定位new表达式****(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:
new (place_address) type 或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是 配合内存池使用 。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

cpp 复制代码
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
A* p1 = (A*)malloc(sizeof(A));
new(p1)A; // 如果A类的构造函数有参数时,此处需要传参
p1->~A();
free(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);
p2->~A();
operator delete(p2);
return 0;
}

由于内容太过超前,作者暂且介绍这么多~
- - - - - - ------------------------------本文结束------------------------------ - - - - - -

相关推荐
猷咪8 分钟前
C++基础
开发语言·c++
IT·小灰灰10 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧11 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q12 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳012 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾12 分钟前
php 对接deepseek
android·开发语言·php
CSDN_RTKLIB15 分钟前
WideCharToMultiByte与T2A
c++
2601_9498683616 分钟前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
星火开发设计30 分钟前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
蒹葭玉树41 分钟前
【C++上岸】C++常见面试题目--操作系统篇(第二十八期)
linux·c++·面试