在前面的学习中,我们已经掌握了C语言的动态内存管理,包括 malloc
,realloc
,calloc
,free
等用于动态开辟和释放内存的函数,忘记了?没关系,点击一键复习 C语言动态内存管理
那么C++和C语言在内存管理方面有哪些不同之处呢?本文带大家一探究竟!
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);
}
看一段代码,思考以下问题:
选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar
在哪里?____staticGlobalVar
在哪里?____staticVar
在哪里?____localVar
在哪里?____num1
在哪里?____char2
在哪里?____*char2
在哪里?___pChar3
在哪里?____*pChar3
在哪里?____ptr1
在哪里?____*ptr1
在哪里?____根据下图不难得出答案,这里我就不在赘述
值得注意的是:
char2是栈上的字符数组,而*char2则指向代码段的只读常量字符串
pChar3是栈上的字符指针变量,而*pChar3同样指向代码段的只读常量字符串
ptr是栈上的整形指针变量,而*ptr1则指向开辟的堆空间
new是一个C++中的一个关键字,也叫做操作符,用于在堆(heap)上动态分配内存,并调用类的构造函数(如果适用)来初始化新分配的对象。new 操作符返回指向分配的内存的指针。使用new开辟内存失败后抛出 std::bad_alloc
异常
delete 同样也是一个操作符,用于释放先前由 new 分配的内存,以防止内存泄漏。它删除之前分配的内存,并将指针置为空,以防止访问已释放的内存。
对于内置类型:
new
操作符允许我们在堆上为变量申请内存空间,并且可以设定初始化的值,如果没有显式给出,则由编译器初始化为随机值。使用完毕后,需要手动的使用delete
操作符释放空间,防止内存泄漏。// 动态申请一个int类型的空间
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
// 编译器警告使用未初始化的内存*ptr1
std::cout << *ptr1 << std::endl;
std::cout << *ptr2 << std::endl;
delete ptr1;
delete ptr2;
输出:
-842150451
10
对于数组类型:
new[]
而释放内存需要使用delete[]
//动态申请3个int类型的空间
int* ptr3 = new int[3];
for (int i = 0; i < 3; ++i)
{
// 同样编译器警告使用未初始化的内存ptr3[]
std::cout << ptr3[i] << std::endl;
}
delete[] ptr3;
输出:
-842150451
-842150451
-842150451
对于自定义类型:
class A
{
public:
A(int a = 0)
: _a(a)
{
std::cout << "A():" << this << std::endl;
}
~A()
{
std::cout << "~A():" << this << std::endl;
}
private:
int _a;
};
A* p1 = new A(1);
delete p1;
有很多同学会有疑惑,new是一个关键字或者叫做操作符,new为什么可以完成对内存的开辟和初始化,甚至它是如何调用自定义类型的构造函数的?
A *p1=new A(3);
在Visual Studio 中,光标定位到new关键字上,按一下F12键,会跳转到一个系统文件的某位置。在该位置处可以发现operator new字样如下(不同版本的内容看上去可能不同):
#endif
_VCRT_EXPORT_STD _NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(
size_t _Size
);
不难看出,operator new
是一个函数,其返回值为void*
,参数为size_t _Size
由此我们推测,new
的底层实现很可能调用了operator new
!
可以在“A *p1=new A(3);”代码行处设置一个断点,并按F5键(或选择“调试”→“开始调试”命令)进行调试,当断点停留在该代码行时,通过选择“调试”→“窗口”→“反汇编”命令打开反汇编窗口,这样就可以看到这行代码对应的汇编语言代码是什么,如图所示。
不难发现,new关键字主要做了两件事:
operator new
;继续调试中可以使用F11键(或选择“调试”→“逐语句”命令)跳转进operator new
,发现operator new
调用了malloc
,如图所示。
这里就会有同学发出疑问了,operator new
是什么?为什么new
要 调用 operator new
呢?
实际上operator new是 C++标准库中的库函数,函数原型为:
//throwing (1)
void* operator new (std::size_t size);
//nothrow (2)
void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
//placement (3)
void* operator new (std::size_t size, void* ptr) noexcept;
那么既然它是一个函数,就一定可以被调用,我们在main函数中添加如下一行代码尝试调用operator new
int *p=(int*)operator new(3);
成功编译,如果计算机上安装了 Visual Studio ,在其安装目录下的某个子目录中会存在一个叫作new_scalar.cpp的文件,operator new
的实现源码就在该文件中。
我的文件在如下路径:
Visual Studio 2022\VC\Tools\MSVC\14.41.34120\crt\src\vcruntime
源码类似如下:
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}
if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
}
}
operator new
在分配内存失败时,会抛出std::bad_alloc
异常,这也就是为什么new
在分配内存失败时,会抛出std::bad_alloc
异常的原因operator new
来完成内存分配,而operator new
最终调用malloc
完成内存分配!根据上面这些线索,我们进行如下小结:
A *pa = new A(); //操作符
operator new(); //函数
malloc(); //C函数
A::A(); //有构造函数就调用构造函数
进而可以写出new关键字分配内存时的大概调用关系。new关键字的调用关系如下表示:
delete
释放刚才通过new
动态开辟的内存A *p1=new A(3);
delete p1;
设置断点到delete p1
;行并进行跟踪调试,通过反汇编窗口,进入反汇编我们可以看到delete具体干了什么,如下图:
根据反汇编我们可以看出,delete 首先调用了类A的析构函数来释放资源,然后继续逐语句调试,我们跳转进入类A的析构函数内部查看反汇编代码如下:
operator delete
函数继续调试中可以使用F11键(或选择“调试”→“逐语句”命令)跳转进operator delete
,发现operator delete
调用了free函数
,如图所示。
operator delete
实际上也是C++标准库当中的库函数,而它在底层调用了free
函数完成了对内存的释放。 函数原型为://ordinary (1)
void operator delete (void* ptr) noexcept;
//nothrow (2)
void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) noexcept;
//placement (3)
void operator delete (void* ptr, void* voidptr2) noexcept;
如果计算机上安装了Visual Studio ,在其安装目录下的某个子目录中会存在一个叫作delete_scalar.cpp 的文件,operator delete
的实现源码就在该文件中。 我的路径为:
Visual Studio 2022\VC\Tools\MSVC\14.41.34120\crt\src\vcruntime
_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
#ifdef _DEBUG
_free_dbg(block, _UNKNOWN_BLOCK);
#else
free(block);
#endif
}
最终可以写出delete关键字释放内存时的大概调用关系(注意调用顺序)如下:
delete pa;
A::~A(); // 如果有析构函数,则先调用析构函数
operator delete(); //函数
free(); //C函数释放内存
读到这里,我们已经知道了new和delete在底层的调用顺序,那么还有一些关于new和delete的细节问题需要我们知道.
new delete
和 new[] delete[]
的配对使用
在 《Effective C++》中有这么一段话:如果你在 new 表达式中使用 [ ],必须在相应的 delete 表达式中也使用 [ ]。如果你在 new 表达式中不使用 [ ],一定不要在相应的 delete 表达式中使用 [ ]。
为什么new new[]
、delete delete[]
必须要配对使用呢?
关于这个问题,我们先来看看 new[]
和delete[]的底层实现
实际上,当我们使用new[]操作符时,我们会根据数组的大小多次调用new操作符来完成对内存的申请和初始化(对内置类型直接赋值或者调用自定义类型的构造函数) 这里我们在如下代码上打上断点
A *p = new A[3];
并按F5键(或选择“调试”→“开始调试”命令)进行调试,当断点停留在该代码行时,通过选择“调试”→“窗口”→“反汇编”命令打开反汇编窗口,可以看到如下界面:
operator new[]
同样是C++标准库中的库函数,原型如下:
//throwing (1)
void* operator new[] (std::size_t size);
//nothrow (2)
void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
//placement (3)
void* operator new[] (std::size_t size, void* ptr) noexcept;
我们继续调试中可以使用F11键(或选择“调试”→“逐语句”命令)跳转进operator new[]
,发现operator new[]
调用了operator new
,如图所示。
我们看到实际上operator new[]
在内部通过多次调用operator new
来完成开辟空间和初始化的任务,那么究竟要调用多少次?换句话说,operator new[]怎么知道要申请多少空间呢?
如下图所示:
new[]
操作符时,new[]
除了为其分配指定空间外,还会额外多开辟 4 个字节用来存储实际分配的对象个数,这个信息就存储在 块头 中。new
操作符则不会保留存储信息delete[]
调用析构函数的次数。new[]
为用户返回的地址并不是从块头开始,而是忽略了块头内的存储信息,返回为对象分配的起始地址。聊完这个,我们再来看delete[],使用delete[]对我们刚才申请的内存释放:
delete[] p;
同样转到反汇编,可以看到如下界面:
发现delete[]
先调用了类A的析构函数,接着我们跳转到析构函数内部:
我们发现,调用了析构函数之后,delete[]
实际上调用operator delete[]
完成对对象资源的清理。
同理,operator delete[]
也是C++标准库中的库函数,原型如下:
ordinary (1)
void operator delete[] (void* ptr) noexcept;
nothrow (2)
void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) noexcept;
placement (3)
void operator delete[] (void* ptr, void* voidptr2) noexcept;
operator delete[]
内部,发现operator delete[]
最终调用了operator delete
那么,delete[]
调用析构函数的次数就显得尤为重要了,还记得我们前面提到的new[]
开辟内存时会多开辟4个字节作为块头,存储数组内的对象个数吗。此时当delete[]需要释放空间时,将传入的 起始地址向前偏移4个字节 就可以拿到块头中的存储信息,也就可以确实析构函数的调用次数,合理的释放内存。
看到这里,想必你已经明白为什么前面提到的为什么使用new
开辟的内存必须要用delete
释放,而使用new[]
开辟的内存必须要用delete[]
释放。
new
开辟的空间,用delete[]
释放会发生什么问题?A* p = new A(1);
delete[] p;
在C++标准中,这样做会导致未定义的行为,不同的编译器具体处理方式不同,可能会造成以下错误:
delete[]
的实现逻辑是按照数组的方式去释放内存,它会试图按照数组的规则去调用多次析构函数等操作,而实际指向的只是一个对象,这可能会破坏内存结构等。我们说过delete[]
会将需要释放对象的地址向前偏移 4 个字节来找到块头内的存储信息,而new
在开辟空间时不会额外开辟 4 个字节来存储对象个数,这样一来就会发生未定义的错误!
在Linux平台下的运行错误:
A():0x1e73c20
~A():0x1e73ca0
~A():0x1e73c9c
~A():0x1e73c98
~A():0x1e73c94
~A():0x1e73c90
~A():0x1e73c8c
~A():0x1e73c88
~A():0x1e73c84
~A():0x1e73c80
~A():0x1e73c7c
~A():0x1e73c78
~A():0x1e73c74
~A():0x1e73c70
~A():0x1e73c6c
~A():0x1e73c68
~A():0x1e73c64
~A():0x1e73c60
~A():0x1e73c5c
~A():0x1e73c58
~A():0x1e73c54
~A():0x1e73c50
~A():0x1e73c4c
~A():0x1e73c48
~A():0x1e73c44
~A():0x1e73c40
~A():0x1e73c3c
~A():0x1e73c38
~A():0x1e73c34
~A():0x1e73c30
~A():0x1e73c2c
~A():0x1e73c28
~A():0x1e73c24
~A():0x1e73c20
*** Error in `./code': free(): invalid pointer: 0x0000000001e73c18 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x81329)[0x7fc444535329]
./code[0x400a5c]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7fc4444d6555]
./code[0x400929]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:01 1056886 /home/zwy/code/Lesson/cplusplus/code
00601000-00602000 r--p 00001000 fd:01 1056886 /home/zwy/code/Lesson/cplusplus/code
00602000-00603000 rw-p 00002000 fd:01 1056886 /home/zwy/code/Lesson/cplusplus/code
01e62000-01e94000 rw-p 00000000 00:00 0 [heap]
7fc440000000-7fc440021000 rw-p 00000000 00:00 0
7fc440021000-7fc444000000 ---p 00000000 00:00 0
7fc4444b4000-7fc444678000 r-xp 00000000 fd:01 132472 /usr/lib64/libc-2.17.so
7fc444678000-7fc444877000 ---p 001c4000 fd:01 132472 /usr/lib64/libc-2.17.so
7fc444877000-7fc44487b000 r--p 001c3000 fd:01 132472 /usr/lib64/libc-2.17.so
7fc44487b000-7fc44487d000 rw-p 001c7000 fd:01 132472 /usr/lib64/libc-2.17.so
7fc44487d000-7fc444882000 rw-p 00000000 00:00 0
7fc444882000-7fc444897000 r-xp 00000000 fd:01 131093 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7fc444897000-7fc444a96000 ---p 00015000 fd:01 131093 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7fc444a96000-7fc444a97000 r--p 00014000 fd:01 131093 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7fc444a97000-7fc444a98000 rw-p 00015000 fd:01 131093 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7fc444a98000-7fc444b99000 r-xp 00000000 fd:01 132480 /usr/lib64/libm-2.17.so
7fc444b99000-7fc444d98000 ---p 00101000 fd:01 132480 /usr/lib64/libm-2.17.so
7fc444d98000-7fc444d99000 r--p 00100000 fd:01 132480 /usr/lib64/libm-2.17.so
7fc444d99000-7fc444d9a000 rw-p 00101000 fd:01 132480 /usr/lib64/libm-2.17.so
7fc444d9a000-7fc444f0b000 r-xp 00000000 fd:01 926012 /home/zwy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6
7fc444f0b000-7fc44510b000 ---p 00171000 fd:01 926012 /home/zwy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6
7fc44510b000-7fc445115000 r--p 00171000 fd:01 926012 /home/zwy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6
7fc445115000-7fc445117000 rw-p 0017b000 fd:01 926012 /home/zwy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6
7fc445117000-7fc44511b000 rw-p 00000000 00:00 0
7fc44511b000-7fc44513d000 r-xp 00000000 fd:01 138861 /usr/lib64/ld-2.17.so
7fc445330000-7fc445335000 rw-p 00000000 00:00 0
7fc445339000-7fc44533c000 rw-p 00000000 00:00 0
7fc44533c000-7fc44533d000 r--p 00021000 fd:01 138861 /usr/lib64/ld-2.17.so
7fc44533d000-7fc44533e000 rw-p 00022000 fd:01 138861 /usr/lib64/ld-2.17.so
7fc44533e000-7fc44533f000 rw-p 00000000 00:00 0
7ffcdbb05000-7ffcdbb26000 rw-p 00000000 00:00 0 [stack]
7ffcdbba8000-7ffcdbbaa000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Aborted
错误提示:
*** Error in ./code': free(): invalid pointer: 0x0000000001e73c18 ***
表明程序在运行时遇到了错误,free 函数(delete[]最终会调用到底层的free等内存释放函数)检测到传入了一个无效的指针。这是因为delete []` 以处理数组的方式去释放单个对象内存,破坏了内存管理的内部结构,导致后续的内存释放操作失败。
回溯信息(Backtrace):
======= Backtrace: ========
后面的内容是程序崩溃时的函数调用栈回溯信息,显示了程序在崩溃时调用了哪些函数以及这些函数在内存中的地址等信息。通过这些信息可以辅助定位错误发生的位置和调用逻辑,但这里错误的根源是 new 和 delete[] 不匹配的内存释放操作。
内存映射(Memory map):
======= Memory map: ========
后面的内容是程序运行时的内存映射情况,显示了各个动态链接库、程序代码段、数据段、堆、栈等在内存中的地址范围和权限等信息。
new[]
开辟的空间,用delete
释放会发生什么问题? A* p = new A[3];
delete p;
在C++标准中,这样做会同样导致未定义的行为,不同的编译器具体处理方式不同。
对于自定义类型A,delete
只会调用一次析构函数,而不是为数组中的每个对象调用,这样会导致数组中其他对象的资源无法正常释放,造成内存泄漏。
在Linux平台下的运行错误:
A():0x1ce3c28
A():0x1ce3c2c
A():0x1ce3c30
~A():0x1ce3c28
*** Error in `./code': munmap_chunk(): invalid pointer: 0x0000000001ce3c28 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x7f474)[0x7f5861fd0474]
./code[0x400a5b]
/lib64/libc.so.6(__libc_start_main+0xf5)[0x7f5861f73555]
./code[0x400929]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:01 1056886 /home/zwy/code/Lesson/cplusplus/code
00601000-00602000 r--p 00001000 fd:01 1056886 /home/zwy/code/Lesson/cplusplus/code
00602000-00603000 rw-p 00002000 fd:01 1056886 /home/zwy/code/Lesson/cplusplus/code
01cd2000-01d04000 rw-p 00000000 00:00 0 [heap]
7f5861f51000-7f5862115000 r-xp 00000000 fd:01 132472 /usr/lib64/libc-2.17.so
7f5862115000-7f5862314000 ---p 001c4000 fd:01 132472 /usr/lib64/libc-2.17.so
7f5862314000-7f5862318000 r--p 001c3000 fd:01 132472 /usr/lib64/libc-2.17.so
7f5862318000-7f586231a000 rw-p 001c7000 fd:01 132472 /usr/lib64/libc-2.17.so
7f586231a000-7f586231f000 rw-p 00000000 00:00 0
7f586231f000-7f5862334000 r-xp 00000000 fd:01 131093 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f5862334000-7f5862533000 ---p 00015000 fd:01 131093 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f5862533000-7f5862534000 r--p 00014000 fd:01 131093 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f5862534000-7f5862535000 rw-p 00015000 fd:01 131093 /usr/lib64/libgcc_s-4.8.5-20150702.so.1
7f5862535000-7f5862636000 r-xp 00000000 fd:01 132480 /usr/lib64/libm-2.17.so
7f5862636000-7f5862835000 ---p 00101000 fd:01 132480 /usr/lib64/libm-2.17.so
7f5862835000-7f5862836000 r--p 00100000 fd:01 132480 /usr/lib64/libm-2.17.so
7f5862836000-7f5862837000 rw-p 00101000 fd:01 132480 /usr/lib64/libm-2.17.so
7f5862837000-7f58629a8000 r-xp 00000000 fd:01 926012 /home/zwy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6
7f58629a8000-7f5862ba8000 ---p 00171000 fd:01 926012 /home/zwy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6
7f5862ba8000-7f5862bb2000 r--p 00171000 fd:01 926012 /home/zwy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6
7f5862bb2000-7f5862bb4000 rw-p 0017b000 fd:01 926012 /home/zwy/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6
7f5862bb4000-7f5862bb8000 rw-p 00000000 00:00 0
7f5862bb8000-7f5862bda000 r-xp 00000000 fd:01 138861 /usr/lib64/ld-2.17.so
7f5862dcd000-7f5862dd2000 rw-p 00000000 00:00 0
7f5862dd6000-7f5862dd9000 rw-p 00000000 00:00 0
7f5862dd9000-7f5862dda000 r--p 00021000 fd:01 138861 /usr/lib64/ld-2.17.so
7f5862dda000-7f5862ddb000 rw-p 00022000 fd:01 138861 /usr/lib64/ld-2.17.so
7f5862ddb000-7f5862ddc000 rw-p 00000000 00:00 0
7ffc8a3db000-7ffc8a3fc000 rw-p 00000000 00:00 0 [stack]
7ffc8a3fc000-7ffc8a3fe000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Aborted
Visual Studio 2022平台下程序崩溃:
根据终端的错误信息,我们可以看到:
这样会导致剩下两个对象的资源无法正确释放,从而导致内存泄漏!
程序崩溃的可能原因 :
delete 操作在处理内存时,其逻辑与 delete[] 不同,它并不知道之前 new[]分配内存时存储在头部的额外存储信息。delete可能会错误地解释内存结构,破坏堆内存管理器维护的内部数据结构(比如内存块的链表、元数据等)。当后续程序尝试再次进行内存分配或释放等操作时,堆内存管理器可能会因为这些被破坏的数据结构,无法正确管理内存,进而引发运行时错误,最终导致程序崩溃。
new
和delete[]
混用:
int* p = new int;
delete[] p;
以及new[]
和delete
混用:
int* q = new int[3];
delete q;
这两种情况在Linux平台 和 Visual Studio 2022平台下程序都可以正常运行结束,似乎对于内置类型来说这样写是正确的,事实确并非如此:
int
、char
、double
等,它们没有复杂的构造函数和析构函数逻。当使用new分配单个内置类型对象的内存,然后使用delete[]来释放,或者相反,在基本的内存释放操作上可能看起来没有问题,因为没有需要特别处理的析构过程来清理资源或执行其他复杂操作,系统只是简单地将分配的内存回收。new/delete
和new[]/delete[]
时,从内存地址的角度看,似乎能够正确地找到并释放相应的内存空间,不会因为内存结构的复杂性而导致立即出现明显的错误,比如内存访问冲突或程序崩溃等。实际上这种做法仍然是错误的,存在着极大的风险,因此无论是内置类型还是自定义类型,我们在编程中都应该始终严格遵循 “new 搭配 delete,new[] 搭配 delete[]” 的规则。
类型不同:
功能不同:
返回值不同:
void*
,必须要手动进行强制类型转换才可以使用内存分配失败的处理不同:
std::bad_alloc
异常。可以使用 std::nothrow
来让new在分配失败时返回 nullptr
而不抛出异常。nullptr
,因此在使用 malloc 分配内存后,需要手动检查返回值是否为 nullptr
来判断内存分配是否成功。内存释放方式不同:
delete
(对于单个对象)或 delete[]
(对于数组对象)来释放,delete会调用对象的析构函数,确保对象的资源被正确释放。free
函数来释放,free
不会调用对象的析构函数。分配内存的大小指定不同:
sizeof
操作符来计算所需的字节数。比较项 | new | malloc |
---|---|---|
类型 | C++中的关键字/操作符 | C语言中的函数 |
功能 | 分配内存并调用类的构造函数完成初始化(若有构造函数) | 只负责分配指定大小的内存块,不调用构造函数,不初始化内存 |
返回值 | 无需关注返回类型,编译器自动转化 | 默认返回值为void*,需手动强制类型转换 |
内存分配失败处理 | 默认抛出std::bad_alloc异常,可使用std::nothrow使分配失败时返回nullptr | 分配失败时返回nullptr,需手动检查返回值判断是否成功 |
内存释放方式 | 用delete(单个对象)或delete[](数组对象)释放,会调用对象析构函数 | 使用free函数释放,不会调用对象析构函数 |
分配内存大小指定 | 根据对象类型自动计算所需内存大小,无需显式指定 | 需显式指定要分配的内存字节数,常使用sizeof操作符计算 |
类型不同:
<stdlib.h>
(C++ 中为 <cstdlib>
)头文件中声明。
功能不同:
malloc
、calloc
或 realloc
函数分配的内存块。它只是将该内存标记为可被后续分配使用,不会调用对象的析构函数。这意味着如果内存块中存储的是对象,对象内部可能持有的资源(如动态分配的成员变量、打开的文件句柄等)不会被自动清理。使用对象不同:
参数不同:
void*
类型的指针作为参数。这个指针必须是之前通过 malloc、calloc 或 realloc 函数返回的指针。由于 void* 类型的指针可以接收任意类型的指针,所以在传递给 free 时不需要进行显式的类型转换。比较项 | free | delete |
---|---|---|
类型 | C语言标准库函数,在C++中也可使用 | C++中的操作符 |
功能 | 仅释放由malloc、calloc、realloc分配的内存块,不调用对象的析构函数 | 释放由new分配的内存,对于单个对象使用delete,对于数组对象使用delete[],会调用对象的析构函数,确保对象的资源被正确释放 |
使用对象 | 只能用于释放malloc、calloc、realloc返回的指针所指向的内存 | 用于释放new操作符分配的内存 |
安全性 | 不会自动处理对象资源的释放,如果释放的内存中包含对象且有需要释放的资源(如动态分配的成员变量),可能会导致资源泄漏 | 会调用对象的析构函数,能正确处理对象资源的释放,减少资源泄漏的风险 |
参数 | 接受一个void*类型的指针作为参数,该指针必须是之前由malloc、calloc、realloc返回的指针 | 接受一个指向对象的指针作为参数,指针类型必须与new操作时的类型匹配 |
new 和 delete 操作符:
操作符细节:
C 与 C++ 内存管理对比:
本文到这里就结束了,有关C++内存管理更深入的讲解,如malloc
和free
的源码和底层原理、定位new表达式 placement-new
等高级话题,后面会发布专门的文章为大家讲解。感谢您的观看!