C/C++内存管理 new/delete、malloc/realloc/calloc/free详解

文章目录

  • 一、C/C++内存分布
  • 二、C语言动态内存函数
    • 1.malloc函数
    • 2.free函数
    • 3.calloc函数
    • 4.realloc函数
  • 三、C++内存管理方式
  • 四、operator new和operator delete函数
  • 五、new和delete的实现原理
    • 1.内置类型
    • 2.自定义类型
  • 六、malloc/free和new/delete的区别

一、C/C++内存分布

C/C++中程序区域由6部分组成:内核空间、栈、内存映射段、堆、数据段、代码段,如下图所示:
C/C++内存管理 new/delete、malloc/realloc/calloc/free详解_第1张图片
内存区域划分说明:

  1. 栈:又叫堆栈,在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈主要存储的是非静态局部变量、函数参数、返回值等等,栈是向下增长的
  2. 内存映射段:是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享来共享内存,做进程间通信
  3. 堆:用于程序运行时动态内存分配,堆是可以向上增长的
  4. 数据段:存储全局数据和静态数据
  5. 代码段:可执行的代码/只读常量,存放函数体(类成员函数和全局函数)的二进制代码

二、C语言动态内存函数

1.malloc函数

C语言提供了一个动态内存开辟的函数:

void* malloc (size_t size);

malloc函数的说明:

  1. 这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针
  2. 如果开辟成功,则返回一个指向开辟好空间的指针
  3. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
  4. 返回值的类型是void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定
  5. 如果参数size为0,malloc的行为的标准是未定义的,取决于编译器
int main()
{
	//利用malloc动态开辟数组a
	int* a = (int*)malloc(sizeof(int) * 10);
	//对于是否开辟成功进行检查
	if (a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	return 0;
}

2.free函数

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的:

void free (void* ptr);

free函数的说明:

  1. free函数用来释放动态开辟的内存
  2. 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的
  3. 如果ptr是NULL指针,则函数什么事都不做
int main()
{
	//利用malloc动态开辟数组a
	int* a = (int*)malloc(sizeof(int) * 10);
	//对于是否开辟成功进行检查
	if (a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	//释放动态开辟的数组a
	free(a);
	//将a指针置空
	a = NULL;
	return 0;
}

3.calloc函数

C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配:

void* calloc (size_t num, size_t size);

calloc函数的说明:

  1. 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
  2. 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为0
int main()
{
	//用calloc动态开辟数组a
	int* a = (int*)calloc(10, sizeof(int));
	if (a == NULL)
	{
		printf("calloc fail\n");
		exit(-1);
	}
	//查看a的初始化值
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

数组的初始化值为:
在这里插入图片描述

4.realloc函数

realloc函数能够做到对动态开辟内存大小进行调整:

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

realloc函数的说明:

  1. ptr是要调整的内存地址
  2. size是调整之后的大小
  3. 返回值为调整之后的内存起始位置
  4. 这个函数调整除了调整原内存空间的大小,还会将原来内存中的数据移动到新的空间
  5. realloc在调整内存空间的时候存在两种情况:
    (1)情况一:原有空间之后有足够大的连续空间(如下图),这种情况下要扩展的内存会直接在原有内存之后直接追加空间,原来空间的数据不发生变化
    C/C++内存管理 new/delete、malloc/realloc/calloc/free详解_第2张图片
    (2)情况二:原有空间之后没有足够大的空间(如下图),这种情况下扩展的方法是在堆空间上另找一个合适大小的连续空间来使用,这样函数返回的是一个新的内存地址
    C/C++内存管理 new/delete、malloc/realloc/calloc/free详解_第3张图片
int main()
{
	int* a = (int*)malloc(sizeof(int) * 10);
	if (a == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}

	//先用tmp数组来保存新开辟的空间,防止开辟失败导致原数组数据的丢失
	int* tmp = (int*)realloc(a, sizeof(int) * 20);
	if (tmp == NULL)
	{
		printf("realloc fail\n");
		exit(-1);
	}
	a = tmp;
	return 0;
}

三、C++内存管理方式

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

void Test()
{
    // 动态申请一个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;
}

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

除了内置类型,new/delete在操作自定义类型时也非常的方便:

class Date
{
public:
	Date(int year=1,int month=1,int day=1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date* d1 = (Date*)malloc(sizeof(Date));
	if (d1 == nullptr)
	{
		cout << "malloc fail" << endl;
		exit(-1);
	}

	Date* d2 = new Date;
    
    free(d1);
    delete d2;
	return 0;
}

我们通过调试可以发现,如果是用malloc开辟的动态内存,并不会完成初始化赋值,而用new开辟的动态内存会调用自定义类型的构造函数去完成初始化赋值
C/C++内存管理 new/delete、malloc/realloc/calloc/free详解_第4张图片
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会


四、operator new和operator delete函数

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

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;
}

通过上述两个全局函数的实现我们可以知道,operator new实际也是通过malloc来申请空间,如果malloc申请空间成功则直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete最终是通过free来释放空间的。


五、new和delete的实现原理

1.内置类型

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

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

2.自定义类型

  1. new的原理:
    (1)调用operator new函数申请空间
    (2)在申请的空间上执行构造函数,完成对象的构造
  2. delete的原理
    (1)在空间上执行析构函数,完成对象中资源的清理工作
    (2)调用operator delete函数释放对象的空间
  3. new T[N]的原理
    (1)调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
    (2)在申请的空间上执行N次构造函数
  4. delete[]的原理
    (1)在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
    (2)调用operator delete[]释放空间,实际在operator delete[]中调用operator 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不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

你可能感兴趣的:(c++,c语言,java)