目录
[operator new和operato delete函数](#operator new和operato delete函数)
内存管理的划分
C/C++ 程序运行时内存主要划分为栈、堆、全局静态区、常量区、代码区,还有内存映射段。
栈由编译器自动分配与释放,存放非静态局部变量、函数参数、函数返回值,栈向下增长,函数调用结束后栈上的数据会自动回收,不需要程序员手动管理。
堆用于程序运行过程中的动态内存分配,由程序员手动申请和释放,malloc、new 都是在堆上开辟空间,free、delete 完成释放,堆向上增长,如果申请的堆内存没有释放,就会发生内存泄漏。
全局静态区存放全局变量和 static 修饰的静态变量,分为已初始化和未初始化两个部分,这块内存在程序启动的时候分配,直到程序结束才释放。常量区存放字符串字面量等只读常量,该区域的内容不允许修改,强行修改会造成程序崩溃
了解这些知识以后,我们来看下面的一段代码和相关问题。
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);
}
- 选择题:
选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____
staticGlobalVar在哪里?____
staticVar在哪里?____
localVar在哪里?____
num1 在哪里?____
char2在哪里?____
* char2在哪里?___
pChar3在哪里?____
* pChar3在哪里?____
ptr1在哪里?____
* ptr1在哪里?____
- 填空题:
sizeof(num1) = ____;
sizeof(char2) = ____;
strlen(char2) = ____;
sizeof(pChar3) = ____;
strlen(pChar3) = ____;
sizeof(ptr1) = ____;
- sizeof 和 strlen 区别?
- globalVar 是全局变量,全局变量存放在数据段也就是静态区,选C。
- staticGlobalVar 是静态全局变量,static 修饰的全局变量依旧存放在数据段静态区,只是作用域被限制在当前文件,选 C。
- staticVar 是函数内部的静态局部变量,普通局部变量在栈,但加 static 修饰后,变量不再放在栈,而是放到数据段静态区,程序结束才销毁,选C。
- localVar 是函数内普通局部变量,局部变量创建在栈上,函数调用结束后栈空间自动回收,选A。
- num1 是函数内部定义的局部数组,数组属于局部变量,数组开辟的内存空间在栈上,选A。
- char2 是函数内的局部字符数组,数组本身是局部变量,存储在栈上,字符串 "abcd" 的内容会拷贝到这个栈数组里,所以 char2 在栈,选A。
- *char2 代表 char2 数组里的第一个字符,数组整体在栈,数组里的元素也存放在栈空间,选A。
- pChar3 是一个局部指针变量,指针变量本身是局部变量,变量本体存放在栈,选 A。
- *pChar3 是指针 pChar3 指向的内容,字符串字面量 "abcd" 存放在常量区,指针指向这个常量字符串,所以pChar3 在常量区,选 D。
- ptr1 是局部的指针变量,指针这个变量本身是局部变量,存放在栈,选 A。
- *ptr1 是 ptr1 指针指向的内存,ptr1 由 malloc 在堆上开辟内存,所以ptr1 访问堆空间,选B。
- sizeof (num1),num1 是 int 类型数组,一共有 10 个元素,单个 int 占 4 字节,10 乘 4 等于 40,所以结果是 40。
- sizeof (char2),char2 是字符数组,用 "abcd" 初始化,数组里面会存放 a、b、c、d 还有字符串结束符 '\0',一共 5 个 char,一个 char 占 1 字节,所以结果是 5。
- strlen (char2),strlen 统计字符串有效字符数量,不算末尾的 '\0',abcd 一共 4 个有效字符,所以结果是 4。
- sizeof (pChar3),pChar3 是 char 类型的指针变量,在 32 位环境指针占 4 字节,64 位环境指针占 8 字节。
- strlen (*pChar3),*pChar3 指向常量字符串 "abcd",有效字符是 a、b、c、d,不统计 '\0',结果是 4。
- sizeof (ptr1),ptr1 是 int 类型的指针变量,指针变量存储的是地址,64 位环境下地址占 8 字节,结果为 8。
- sizeof 是运算符,在编译阶段计算,用来获取变量或者类型占用内存的总字节数,它会包含字符串末尾的 '\0',可以用于所有类型。
- strlen 是库函数,运行时执行,只针对以 '\0' 结尾的字符串,统计 '\0' 之前有效字符的个数,不包含结束符 '\0'。

C语言中动态内存管理的方式
C 语言动态内存管理依靠四个库函数实现。
malloc 函数用来申请一块未初始化的连续堆内存,参数是需要开辟的总字节数,返回 void * 类型的指针,使用的时候需要强制转换成对应类型的指针。如果内存开辟失败,会返回 NULL 空指针,使用前要判断返回值是否为空。开辟出来的内存里面的值是随机垃圾值,不会自动清零。
calloc 函数用来申请指定数量、指定大小的连续堆内存,有两个参数,第一个是元素个数,第二个是单个元素占用字节。它会把申请到的这块内存全部初始化为 0,返回值同样是 void * 指针,开辟失败返回 NULL。
realloc 函数用来调整已经开辟好的堆内存大小,第一个参数是旧内存的指针,第二个参数是调整之后新的总字节数。它会根据情况选择原地扩容或者重新找一块新空间拷贝原有数据,释放旧空间。如果第二个参数设置为 0,效果近似 free。realloc 失败的时候会返回 NULL,原来的内存不会释放,所以不能直接用原指针接收返回值,防止原地址丢失。
free 函数用来释放堆上申请的内存,参数是动态内存的指针。free 只会把这块内存的使用权交还给操作系统,不会把指针本身置空,所以 free 之后建议手动把指针赋值为 NULL,避免出现野指针。不能释放栈上的内存,也不能重复释放同一块堆内存,否则程序会报错。
这四个函数配合使用,malloc、calloc、realloc 负责申请或者调整堆内存,free 负责释放,堆内存需要程序员手动管理,忘记释放会造成内存泄漏。
C++内存管理方式
new/delete操作内置类型
new 和 delete 负责在堆上分配和回收内存。
申请单个内置类型对象,可以用 new,还可以选择初始化。比如 int* p = new int; 这样只会开辟 4 字节堆内存,里面的值是随机值。如果写成 int* p = new int (10); 就会在开辟内存的同时,把这个 int 空间赋值为 10。释放的时候使用 delete p;。
申请内置类型数组,使用 new \[\],语法是 int* p = new int 10; 开辟 10 个连续 int 空间,默认不初始化,里面是随机值。也可以部分初始化,int* p = new int 10{1,2,3}; 前三个元素赋值为 1、2、3,剩下的元素自动初始化为 0。释放数组内存必须用 delete \[\] p;,不能只用 delete。
内置类型使用 new 和 malloc 有一点区别,new 申请失败默认抛出异常,malloc 失败返回 NULL。语法上 new 直接写类型,不用手动计算字节大小,返回的指针不用强制转换。
使用的时候要注意配对,new 出来的单个变量用 delete 释放,new \[\] 开辟的数组,必须用 delete \[\] 释放。释放之后指针不会自动置空,最好手动把指针赋值成 nullptr,防止野指针。

new/delete操作自定义类型
当 new 和 delete 操作自定义类型的时候,不仅仅是开辟和释放堆内存,还会自动调用构造函数与析构函数。
使用 new 创建单个自定义类型对象时,首先底层会调用 operator new 在堆上开辟++足够大小++ 的内存,开辟成功之后,++自动调用这个类的构造函数++,完成对象的初始化。比如 Date* p = new Date (2026,9,29);,先分配内存,再执行构造函数给年月日赋值。释放这个对象用 delete p;,delete 会++先调用对象的析构函数,清理对象内部的资源++,之后调用 operator 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()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
//还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
如果使用 new \[\] 创建自定义类型的数组,会开辟能够存放多个对象的连续堆内存,然后循环调用每一个对象的构造函数。释放的时候必须使用 delete \[\],它会先循环调用数组里每一个对象的析构函数,再释放整块堆内存。new \[\] 和 delete \[\] 必须配对使用,不能混用普通 delete,否则会出现资源没有完全释放或者程序崩溃的问题。
使用的时候要注意几个要点,new 成功会返回对应类型指针,不需要强制转换,内存申请失败默认抛出bad_alloc 异常。delete 执行完成后,指针变量本身不会自动变成空指针,需要手动赋值为 nullptr,避免产生野指针。同时要保证 new 和 delete 配对,new \[\] 和 delete \[\] 配对,一旦配对错误,析构函数调用次数出错,会引发未定义行为。
operator new和operato delete函数
operator new 和 operator delete 是 C++ 提供的全局库函数,并不是 new 和 delete 运算符。new 运算符底层会调用 operator new 函数来开辟堆内存,delete 运算符底层会调用 operator delete 函数来释放堆内存,这两个函数内部本质上是对 malloc 和 free 做了一层封装。
operator new 的作用是申请指定大小的堆内存,参数是需要开辟的字节数,返回 void * 类型的指针。如果内存申请成功,就返回内存起始地址;如果申请失败,默认会执行异常处理流程,抛出 bad_alloc 异常。它只负责分配原始内存,不会调用构造函数 ,构造函数是 new 运算符在拿到这块内存之后才去调用的。
operator delete 的作用是释放 operator new 开辟出来的内存,参数是 void * 类型的内存地址,它只负责归还堆内存,不会调用析构函数 ,析构函数是 delete 运算符在调用 operator delete 之前执行的。它内部调用 free 完成内存回收。
还有配套的 operator new \[\] 和 operator delete \[\],专门给 new \[\] 和 delete \[\] 使用。new \[\] 会调用 operator new \[\] 申请一块连续内存,之后循环调用多次构造函数;delete \[\] 会先循环调用多次析构函数,再调用 operator delete \[\] 释放整块内存。
需要分清几个关键点:++new 是运算符,不能被重载;operator new 是全局函数,可以重载。++ new 做的两件事,第一件调用 operator new 开辟内存,第二件调用构造函数。delete 做的两件事,第一件调用析构函数,第二件调用 operator delete 释放内存。对于内置类型,因为没有构造析构,new 就基本等价于调用 operator new。
使用的时候要注意,不要手动混淆配对,operator new 申请的内存,要用 operator delete 释。同时 operator new 只是分配原始内存,不会初始化对象,想要在这块内存上创建对象,需要使用定位 new。
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)
new和delete的实现原理
内置类型
对于内置类型来说,new 和 delete 本质上就是封装好的内存申请和释放。
当使用 new 申请单个内置类型,比如 int* p = new int (5),new 运算符内部会调用 operator new 函数,operator new 底层调用 malloc 在堆上开辟对应字节大小的内存。内存开辟成功后,会把 5 赋值到这块内存空间。如果不带括号初始化,开辟出来的内存里面是随机值。释放的时候 delete p,delete 内部调用 operator delete,operator delete 内部调用 free 回收这块堆内存,没有析构函数调用。
当使用 new \[\] 申请内置类型数组,比如 int* p = new int 10{1,2,3},new \[\] 会调用 operator new \[\],底层调用 malloc 开辟一整块连续的内存。可以选择性初始化,给出的元素会赋值进去,剩下的元素自动置 0。释放数组内存要使用 delete \[\] p,delete \[\] 调用 operator delete \[\],底层调用 free 释放整块内存,没有析构函数调用。
自定义类型
自定义类型使用 new 和 delete 的时候,不只是单纯开辟和释放内存,还会自动调用构造函数和析构函数。
对于单个自定义对象,执行 new A (1) 的时候分为两步。第一步调用 operator new 函数,operator new 底层封装 malloc,在堆上开辟对应大小的原始内存。第二步拿到内存地址之后,调用类的构造函数,在这块内存上完成对象初始化,给成员变量赋值。
执行 delete p 的时候同样分为两步。第一步先调用对象的析构函数,清理对象内部管理的资源,比如对象里面动态申请的内存。第二步调用 operator delete 函数,内部调用 free,把堆内存归还给操作系统。
如果使用 new \[\] 创建自定义类型数组,比如 new A 10。第一步调用 operator new \[\] 开辟一整块连续的堆内存。第二步循环调用 10 次构造函数,依次初始化数组里每一个对象。释放的时候必须使用 delete \[\],delete \[\] 会先循环调用 10 次析构函数,逐个清理数组内所有对象,之后调用 operator delete \[\] 释放整块内存。
operator new 只负责开辟内存,不会创建对象;构造函数才负责初始化对象。同理 operator delete 只负责回收内存,析构函数负责清理资源。

定位new表达式
定位 new 也叫 placement new,它是 new 的一种特殊用法,它不会在堆上申请新的内存,只是在一块已经提前分配好的内存空间上调用构造函数,创建对象。
定位 new 的语法是new (地址) 类型 (初始化参数)。使用的前提是这块内存已经提前准备好,可以是 malloc 开辟的堆内存,也可以是提前准备好的缓冲区内存。定位 new 只负责调用构造函数,不会分配内存,所以不会抛出 bad_alloc 异常。
举个例子,先用 malloc 申请一块足够存放 A 对象的内存,A* p = (A*) malloc (sizeof (A));,这时候 p 指向的只是一块原始内存,还不是有效的对象,没有执行构造函数。这时写 new (p) A (1),这就是定位 new,它会在 p 指向的这块内存上调用 A 的构造函数,完成对象初始化,这块内存原本就是 malloc 开辟出来的,定位 new 不会额外再开辟内存。
使用定位 new 构造出来的对象,不能直接使用 delete 释放。因为 delete 会先调用析构函数,再去调用 operator delete 释放内存,而这块内存不是 new 申请的,直接 delete 会出错。正确的销毁步骤分为两步,第一步手动调用对象的析构函数,p->~A ();,第二步再用 free 或者 operator delete 释放这块底层的内存。
定位 new 最主要的使用场景是内存池。内存池会一次性预先开辟一大块内存,之后从这块内存里分出小块空间给对象使用,不需要每次都调用 malloc。分出的空间只是原始内存,就依靠定位 new 来调用构造函数创建对象,销毁的时候手动调用析构,再把内存归还内存池,减少频繁调用 malloc 带来的性能损耗。
需要注意几个要点,定位 new 只是调用构造函数,内存的生命周期由我们自己管理。传入的内存地址空间大小必须足够容纳对象,不然会发生越界。定位 new 不改变内存本身,只是在已有内存上构造对象。还有,定位 new 可以作用于堆内存,也可以作用于栈上预先开辟的缓冲区,但是栈内存不能用 free 释放。
举完整的简单例子,class A 有构造和析构函数,void* buf = malloc (sizeof (A));,new (buf) A (10);,这一步执行构造函数。用完之后,((A*) buf)->~A ();,然后 free (buf);。
定位 new 和普通 new 的区别,普通 new 先调用 operator new 开辟堆内存,再调用构造函数。定位 new 不分配内存,只调用构造函数。普通 delete 同时执行析构和释放内存,定位 new 构造的对象需要分开执行析构和内存释放两步操作。
cpp
// 定位new/replacement new
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
A* p1 = (A*)malloc(sizeof(A));
new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参
p1->~A();//注意,这里不调用析构函数的话,p1是不会自己调用析构函数的,因为p1是指针,是内置类型,不是自定义类型,只有自定义类型才会自动调用构造和析构函数
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 需要包含头文件 stdlib.h 才能使用,new 和 delete 不需要头文件。malloc 申请内存时需要手动计算要开辟的字节大小,返回值是 void * 类型,使用时必须强制转换成对应类型的指针;new 后面直接写类型,编译器自动计算大小,返回对应类型的指针,不需要强制转换。内存申请失败时,malloc 会返回 NULL 空指针,需要手动判断返回值是否为空;new 默认会抛出 bad_alloc 异常,需要用异常捕获机制处理。
对于自定义类型,malloc 只会开辟一块原始堆内存,不会调用构造函数,free 只会释放内存,不会调用析构函数。new 会先开辟内存,再自动调用构造函数完成对象初始化;delete 会先调用析构函数清理对象内部资源,再释放堆内存,这是 C++ 里二者最核心的差别。对于内置类型,new/delete 和 malloc/free 功能大体相近,new 额外支持申请内存时直接初始化。
数组开辟区别:malloc 开辟数组直接计算总字节数;new \[\] 用来开辟对象数组,会循环调用多次构造函数,释放数组必须使用 delete \[\]。
内存释放规区别:free 只负责归还内存,不会修改指针本身,释放后指针依旧保留原来地址,容易形成野指针;delete 同样不会自动把指针置空,用完后都建议手动将指针置为 nullptr。
还有底层关系的区别:new 底层会调用 operator new,operator new 内部封装 malloc;delete 底层调用 operator delete,内部封装 free。但是不能简单认为 new 等于 malloc 加构造函数,二者语法、异常机制、对自定义类型的处理都存在差异。同时要注意配对使用,malloc 对应 free,new 对应 delete,new \[\] 对应 delete \[\],不能交叉混用。
适用场景上,C 语言只能使用 malloc 和 free。C++ 中如果是自定义类型,优先使用 new 和 delete,自动调用构造析构;如果只需要单纯开辟一块原始内存,也可以继续使用 malloc 和 free。