C++内存管理

文章目录

  • 前言
  • [一. C/C++内存分布](#一. C/C++内存分布)
  • [二. C语言中动态内存管理方式](#二. C语言中动态内存管理方式)
    • [2.1 malloc函数](#2.1 malloc函数)
    • [2.2 realloc函数](#2.2 realloc函数)
    • [2.3 calloc函数](#2.3 calloc函数)
    • [2.4 free函数](#2.4 free函数)
    • [2.5 总结区别](#2.5 总结区别)
  • [三. C++内存管理方式](#三. C++内存管理方式)
    • [3.1 new/delete操作内置类型](#3.1 new/delete操作内置类型)
    • [3.2 new/delete操作自定义类型](#3.2 new/delete操作自定义类型)
  • [四. operator new与operator delete函数](#四. operator new与operator delete函数)
  • [五. new和delete的实现原理](#五. new和delete的实现原理)
    • [5.1 内置类型](#5.1 内置类型)
    • [5.2 自定义类型](#5.2 自定义类型)
      • [5.2.1 new的原理](#5.2.1 new的原理)
      • [5.2.2 delete的原理](#5.2.2 delete的原理)
      • [5.2.3 new T[N] 的原理](#5.2.3 new T[N] 的原理)
      • [5.2.4 delete[] 的原理](#5.2.4 delete[] 的原理)
  • [六. 定位new表达式](#六. 定位new表达式)
  • [七. malloc/free和new/delete的区别](#七. malloc/free和new/delete的区别)
  • END

前言

在这篇博文中,我会详细介绍C/C++的内存管理并对比它们的内存管理方式。

一. C/C++内存分布

回顾之前的知识,我们先来看下面的一段代码和相关问题:

cpp 复制代码
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";
	const 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);
}

对于以上问题,这些全局变量,局部变量,静态变量或常量都存储在哪些区域?

说明

(Stack)

定义:栈是一种遵循后进先出(LIFO)原则的数据结构,用于存储局部变量函数参数返回地址

特点:自动分配和释放,由编译器管理;存储生命周期与函数调用相关,函数调用时分配,函数返回时释放;存储空间较小,访问速度快
(Heap)

定义:堆是用于动态内存分配 的内存区域,通常需要手动管理。

特点:通过malloc、new等函数分配内存,通过free、delete等函数释放内存;存储生命周期由程序员控制,容易出现内存泄漏和野指针;存储空间较大,但访问速度较栈慢
静态区 (Static Area)

定义:静态区用于存储全局变量静态变量 ,包括在程序整个运行期间都存在的数据,它们的内存空间在程序编译时就已经分配好。

特点:存储生命周期与程序运行周期相同,程序结束时释放;存储空间相对较小,访问速度较快;未初始化的全局变量和静态变量会被自动初始化为0(对于基本数据类型)和空指针(对于指针类型)。
常量区 (Constant Area)

定义:常量区主要用于存储常量数据 ,这些数据在程序运行期间是不可修改的。

特点:常量区的数据只是可读的 ,任何试图修改常量区数据的操作都会导致程序运行时错误;编译器会为常量区的数据分配内存空间,并且这些空间在程序的整个运行期间保持不变;访问速度较快,因为常量数据通常被缓存。

对以上选择题中的 *char2 在哪里可能会有疑问,以下做详细解释:

1.char2是一个字符串数组,并用字符串字面量"abcd"来初始化。

2.字符串字面量"abcd"是存储在只读数据段(常量区)的,因为它是一个不可修改的常量值。

3.char2数组本身存储在栈上,因为它是一个局部数组,数组char2包含了字符串"abcd"的副本,这个副本是可修改的,并且它的生命周期仅限于函数的执行。

4.char2数组的每个元素(即'a', 'b', 'c', 'd')实际上是存储在栈上的,而不是常量区。这些元素是对存储在常量区的字符串字面量的拷贝。

5.在C/C++中,数组名代表的是数组第一个元素的地址 。因此,char2是指向数组char2第一个元素的指针,这个指针指向的是存储在栈上的字符 'a' 的地址。*char2是对指向数组char2第一个元素的指针的解引用,得到的是字符 'a'

所以,*char2 得到的值是存储在栈上的,而不是常量区。常量区只存储了原始的字符串字面量 "abcd",而 char2 数组是这个字符串的一个可修改的副本,存储在栈上。

特别说明:内存映射段 是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。

二. C语言中动态内存管理方式

2.1 malloc函数

cpp 复制代码
void* malloc(size_t size);

size_t是一种无符号整数类型,用于表示内存块的大小(字节数),size参数指定了要分配的字节数。函数返回一个指向所分配内存空间起始地址的void*指针,如果分配失败(例如内存不足),则返回NULL
功能:malloc函数用于在内存的动态存储区(堆)中分配一块指定大小的连续空间。这块内存空间在分配后是未初始化的,其内存是不确定的。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *ptr;
    ptr = (int *)malloc(10 * sizeof(int));
    if (ptr == NULL)
    {
        printf("内存分配失败!\n");
        return 1;
    }
    // 在这里可以使用分配的内存,例如给数组元素赋值
    for (int i = 0; i < 10; i++)
    {
        ptr[i] = i;
    }
    // 使用完后,应该释放内存
    free(ptr);
    return 0;
}

在这个示例中,首先使用了malloc函数分配了10个int类型的内存空间。sizeof(int)用于获取整数类型的字节数,乘以 10 就是所需的总字节数。然后将返回的void*类型指针强制转换为int*类型,以便可以像操作普通数组一样操作这块内存。如果malloc返回NULL,则表示内存分配失败,程序会输出错误信息并退出。最后,使用完内存后通过free函数释放内存。

2.2 realloc函数

cpp 复制代码
void* realloc(void* ptr, size_t size);

ptr是指向原来分配的内存块的指针,size是重新分配后的内存块大小(字节数)。函数返回一个指向重新分配后的内存块起始地址的void*指针。如果分配失败,则返回NULL,原有的内存块仍然有效(相当于对原有的内存块进行扩容),需要使用free函数手动释放。
功能:realloc函数用于修改(重新分配)已经通过malloc、calloc或realloc函数分配的内存块的大小。它可以使内存块扩大或者缩小。如果是扩大内存块,并且原有内存块之后有足够的连续空闲空间,realloc会尝试在原有内存块的基础上进行扩展,不会移动原有数据;如果原有内存块之后没有足够的空间,realloc会另外寻找一块足够大的连续内存空间,将原有数据复制到新的内存块中,然后释放原来的内存块。如果是缩小内存块,数据不会丢失,只是内存块的末尾部分被释放。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *ptr;
    ptr = (int *)malloc(5 * sizeof(int));
    if (ptr == NULL)
    {
        printf("内存分配失败!\n");
        return 1;
    }
    for (int i = 0; i < 5; i++)
    {
        ptr[i] = i;
    }
    // 重新分配内存,使其可以存储10个整数
    ptr = (int *)realloc(ptr, 10 * sizeof(int));
    if (ptr == NULL)
    {
        printf("内存重新分配失败!\n");
        // 需要释放原来分配的内存
        free(ptr);
        return 1;
    }
    // 继续使用扩展后的内存,例如给新的数组元素赋值
    for (int i = 5; i < 10; i++)
    {
        ptr[i] = i;
    }
    free(ptr);
    return 0;
}

在这个示例中,首先使用malloc分配了一个可以存储5个整数的内存块,然后使用realloc函数将其扩展为可以存储10个整数的内存块。如果realloc成功,就可以继续使用新的内存块。最后使用完内存后通过free函数释放内存。

2.3 calloc函数

cpp 复制代码
void* calloc(size_t num, size_t size);

num是要分配的元素个数,size是每个元素的大小(字节数)。函数返回一个指向所分配内存空间起始地址的void*指针,如果分配失败(例如内存不足),则返回NULL。
功能:calloc函数也用于在内存的动态存储区(堆)中分配内存空间,但与malloc不同的是,calloc会将分配的内存空间全部初始化为0。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *ptr;
    ptr = (int *)calloc(10, sizeof(int));
    if (ptr == NULL)
    {
        printf("内存分配失败!\n");
        return 1;
    }
    // 此时数组元素已经被初始化为0,可以直接使用
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", ptr[i]);
    }
    free(ptr);
    return 0;
}

在这个示例中,使用calloc函数分配了一个可以存储10个整数的内存空间,并且由于calloc会自动将内存初始化为0,所以可以直接打印数组元素,它们的值都是0。最后,使用完内存后通过free函数释放内存。

2.4 free函数

cpp 复制代码
void free(void* ptr);

ptr是指向要释放的内存块的指针。调用free函数后,ptr所指向的内存空间被释放,之后程序不能再访问这块内存。
功能:free函数用于释放之前通过malloc、calloc或realloc函数分配的内存空间。如果不释放动态分配的内存,会导致内存泄漏,即程序占用的内存越来越多,最终可能耗尽系统内存资源。

在前面的malloc、realloc和calloc示例中,都有使用free函数来释放动态分配的内存。需要注意的是,不能多次释放同一块内存,也不能释放不是通过动态分配函数(如malloc、calloc、realloc)获得的内存,否则会导致程序出现错误。例如:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
    int *ptr;
    ptr = (int *)malloc(5 * sizeof(int));
    if (ptr == NULL)
    {
        printf("内存分配失败!\n");
        return 1;
    }
    // 正确释放内存
    free(ptr);
    // 错误示范:再次释放已经释放的内存,就会报错
    free(ptr);
    return 0;
}

在这个错误示例中,第二次调用free函数会导致程序出现错误,因为已经释放过的内存不能再次释放。

2.5 总结区别

malloc :只负责分配内存,不负责初始化。
calloc :负责分配内存并初始化为 0。
realloc:可以调整已分配内存块的大小,并且可以处理内存块的移动(如果新大小与原大小不同,可能会移动内存块到新的地址)。

三. C++内存管理方式

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

new
用途 :用于在上动态分配内存,并调用对象的构造函数(如果分配的是对象)。
语法

new 类型:分配一个指定类型的单个对象,并调用其构造函数来初始化。

new 类型[size]:分配一个指定类型的数组,并调用每个元素的构造函数。

返回值 :返回一个指向分配内存的指针,其类型为 类型* 或 类型*[ ](对于数组)。
特点

如果分配失败,会抛出一个 std::bad_alloc 异常

可以分配基本数据类型,对象以及对象数组

分配的对象需要调用其构造函数进行初始化

delete
用途 :用于释放之前通过 new 分配的内存,并调用对象的析构函数(如果删除的是对象)
语法

delete 指针:释放单个对象,并调用其析构函数。

delete[] 指针:释放一个对象数组,并调用每个元素的析构函数。

特点

必须与new 或者 new[] 分配的内存配对使用。

释放后,指针应立即设置为 nullptr ,以避免悬空指针问题。

如果尝试释放未分配的内存或已经释放的内存,结果是未定义的,可能会导致程序崩溃。

3.1 new/delete操作内置类型

cpp 复制代码
void Test()
{
	// 动态申请一个int类型的空间
	int* ptr1 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* ptr2 = new int(10);
	// 动态申请10个int类型的空间,并且进行初始化
	int* ptr3 = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
	delete ptr1;
	delete ptr2;
	delete[] ptr3;
}


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

3.2 new/delete操作自定义类型

对于自定义类型,malloc/free只是简单地开辟空间和释放空间,而new/delete除了会开辟空间和释放空间,还会调用构造函数进行初始化和调用析构函数来清理内存(避免内存泄漏)。

析构函数用于释放对象内部动态分配的其他资源(如在类的成员函数中使用new分配的内存),确保对象在被销毁时能够正确地清理自己的资源。

cpp 复制代码
#include<iostream>
using namespace std;
class A {
public:
	A(int i = 5)
	{
		_p = (int*)malloc(sizeof(int) * i);
		cout << "A()" << endl;
	}
	~A()
	{
		free(_p);
		cout << "~A()" << endl;
	}
private:
	int _a = 1;
	int* _p;
};
int main()
{
	A* a1 = (A*)malloc(sizeof(A));
	free(a1);
	A* a2 = new A;
	delete a2;
	return 0;
}

通过调试可以看到,malloc和free函数没有调用构造函数和析构函数。

继续往下执行可以看到,new调用了构造函数,在delete之前会先调用析构函数将对象内部动态分配的其它资源先释放掉,再释放对象本身。

还可以创建一个A类型对象的指针数组,并且对它们进行初始化。

cpp 复制代码
A* a3 = new A[10];
A* a4 = new A[5]{ 1, 2, 3, 4, 5 };

a3是A类型对象的指针数组(也可以说是指向A类型对象数组的指针),它是一个指针,指向一个包含10个A类型对象的数组的第一个对象,对a3解引用(*a3)得到的是A类型对象数组的第一个对象。

a4同样也是A类型对象的数组,但不同的是,该数组的每个对象都显示调用了构造函数进行初始化,如果不显示调用构造函数,则编译器会自动调用默认构造函数进行初始化(如果没有默认构造则会报错)

我们创建一个类类型对象,可以采用两种方式:A a;A* a = new A;

那么以上两种创建对象的方式有什么区别呢?

1.栈上的对象
A a; 这种方式是在栈上创建一个对象。 这意味着对象a会在声明它的函数或代码块的生命周期内自动创建和销毁。当函数返回或者代码块结束时,对象a会自动调用其析构函数进行清理,不需要显示删除。

  1. 堆上的对象
    A* a = new A; 这种方式是在堆上创建一个对象。 new操作符会分配一块内存来存储对象,并返回指向这块内存的指针。对象的生命周期不再局限于声明它的函数或代码块,而是持续到显示使用delete操作符释放内存为止。如果忘记使用delete删除对象,就会造成内存泄漏。

具体区别如下:

内存位置 :栈上对象存储在栈内存中,而堆上对象存储在堆内存中。
生命周期管理 :栈上对象的生命周期由编译器自动管理,而堆上对象需要程序员手动管理。
内存大小限制 :栈内存通常有大小限制,而堆内存理论上可以更大(受限于操作系统和机器的内存)。
性能 :栈上分配和释放通常更快,因为栈内存是连续的,而堆内存分配可能涉及更复杂的内存管理策略。
异常安全:栈上对象在异常发生时会自动析构,而堆上对象需要手动管理,否则可能导致资源泄漏。

四. operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符,operator newoperator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,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)

以上是C++提供的全局函数operator newoperator delete的源代码。从中可以看出operator new是通过malloc函数来申请空间的,如果申请成功就直接返回;如果申请失败了就尝试执行用户应对空间不足的措施,如果用户没有提供就抛出异常。operator delete是通过free函数来释放空间的(operator new 是对 malloc 的封装,operator delete是对 free 的封装)。

接下来简单演示一下使用try catch来捕获异常和输出异常,因为64位系统的堆内存比较大,所以采用了32位来给大家演示:

cpp 复制代码
#include<iostream>
#include<exception>
using namespace std;
void Func()
{
    // 尝试分配一个非常大的数组,可能会导致堆上空间不足
    int* p1 = new int[100000000]; // 申请一个非常大的数组
    cout << "内存分配成功" << endl;
    int* p2 = new int[100000000]; // 申请一个非常大的数组
    cout << "内存分配成功" << endl;
    int* p3 = new int[100000000]; // 申请一个非常大的数组
    cout << "内存分配成功" << endl;
    int* p4 = new int[100000000]; // 申请一个非常大的数组
    cout << "内存分配成功" << endl;
    int* p5 = new int[100000000]; // 申请一个非常大的数组
    cout << "内存分配成功" << endl;
    int* p6 = new int[100000000]; // 申请一个非常大的数组
    cout << "内存分配成功" << endl;
}
int main() {
    try {
        Func();
    }
    catch (const exception& e) {
        // 捕获std::bad_alloc异常
        cerr << "内存分配失败: " << e.what() << endl;
    }
    return 0;
}

五. new和delete的实现原理

5.1 内置类型

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

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

5.2 自定义类型

5.2.1 new的原理

1.调用 operator new 函数申请空间。

2.在申请的空间上执行构造函数,完成对象的构造。

3.new = (operator new + 构造)

5.2.2 delete的原理

1.在空间上执行析构函数,完成对象中资源的清理工作。

2.调用 operator delete 函数释放对象的空间。

3.delete = (析构 + operator delete)

5.2.3 new T[N] 的原理

1.调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请。

2.在申请的空间上执行N次构造函数。

5.2.4 delete[] 的原理

1.在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。

2.调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。

重点

在使用new[N]时,编译器通过N可以确定要调用多少次构造函数来初始化,但是调用delete[]时没有给出值,那么编译器如何知道要调用多少次析构函数呢?

先来看以下代码:

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* a = new A[10];
	delete[] a;
	return 0;
}

每个对象都有一个成员变量_a,为4字节,申请10个对象所需要40个字节。但是通过内存调试窗口发现一共申请了48个字节,那么多余的8个字节是用来干什么的?

再往上看,就会发现多开出来了8个字节。

内存分配中的记录机制

当使用new[]分配内存时,编译器除了分配足够的空间来存储N个对象,还会在这片内存的开头(或其他合适的位置)存储一些额外的信息。这些信息通常包括对象的数量N以及可能的其他元数据,如内存块的大小等。这个数量N信息在delete[]被调用时会被用来确定需要调用多少次析构函数。

编译器会分配足够的内存来存储N个A类型对象,并且在这内存的开始处存储N的值。当调用delete[] a时,编译器会读取存储在内存开始处的N值,并使用这个值来确定需要调用多少次析构函数。

这个过程是自动的,我们不需要手动指定数组的大小。这也是为什么使用delete[]而不是delete来释放由new[]分配的内存是重要的,因为delete不会读取和使用这个额外的大小信息,这将导致不正确的析构函数调用次数,可能会导致内存泄漏或者未定义行为。

总结
当我们调用new[]创建一个对象数组时,会接收一个地址,这个地址是我们开辟好的连续空间的第一个位置的地址,但是编译器在这个位置之前又多开了空间来存储对象的个数,这样在调用delete[]时就可以根据这个值知道调用多少次析构函数了。如果使用delete[]来释放空间,那么会从a指向的空间再向前移动的位置开始释放空间,而不是从a指向的空间直接开始释放,如果从a指向的空间开始释放就会报错。

比如:

因为调用的是delete a,直接从a指向的位置开始释放空间,而不是从真正的第一个位置开始释放空间(因为申请的空间是连续的,不能分割释放),所以程序报错。

上述情况是我们显示写了析构函数的情况下,如果我们没有显示写析构函数,编译器自动生成的析构函数没有作用,会被优化掉。那么就不会调用析构函数,也不会另外开空间来存储调用次数,所以不会报错。

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* a = new A[10];
	delete a;
	return 0;
}

没有显示写析构函数时,编译器不会报错。

因为编译器自动生成的析构函数不起作用,会被优化掉,所以不会另外开辟空间去存储调用析构函数的次数。

可以参考博客:C++:深入理解operator new/operator delete

六. 定位new表达式

定义:

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。可以理解为:我先开辟好空间用来存储对象,但是不先初始化,等到需要的时候再调用构造函数来初始化对象(定位new)。

使用格式:
new (place_address) type或者new (place_address) type(initializer-list)

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

使用场景:

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

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
// 定位new/replacement new
int main()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参,比如:new(p1)A(1);
	p1->~A();
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

七. 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不需要,但是会抛出 std::bad_alloc 异常,所以new需要捕获异常。

6.申请自定义类型对象时,malloc/free只会开辟空间和释放空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放。

7.使用 malloc 和 free 时,如果忘记释放内存,会导致内存泄漏。使用 new 和 delete 时,如果忘记调用 delete,同样会导致内存泄漏。

8.malloc 和 free 用于分配和释放普通数组,但它们不区分单个对象和数组。new[] 和 delete[] 专门用于分配和释放对象数组,它们会调用每个对象的构造和析构函数。

END

对以上内容有异议或者需要补充的,欢迎大家来讨论!

相关推荐
染指11106 分钟前
50.第二阶段x86游戏实战2-lua获取本地寻路,跨地图寻路和获取当前地图id
c++·windows·lua·游戏安全·反游戏外挂·游戏逆向·luastudio
Code out the future24 分钟前
【C++——临时对象,const T&】
开发语言·c++
Stark、27 分钟前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端
taoyong00128 分钟前
Java线程核心01-中断线程的理论原理
java·开发语言
一雨方知深秋29 分钟前
智慧商城:封装getters实现动态统计 + 全选反选功能
开发语言·javascript·vue2·foreach·find·every
海威的技术博客31 分钟前
关于JS中的this指向问题
开发语言·javascript·ecmascript
sam-zy44 分钟前
MFC用List Control 和Picture控件实现界面切换效果
c++·mfc
froginwe111 小时前
PostgreSQL表达式的类型
开发语言
委婉待续1 小时前
java抽奖系统(八)
java·开发语言·状态模式
deja vu水中芭蕾1 小时前
嵌入式C面试
c语言·开发语言