本文代码已同步Github
一、C/C++内存分布
C/C++ 程序运行时的虚拟内存空间(从低地址到高地址)通常划分为以下几个核心区域:
1. 代码段(.text)
- 存放内容:CPU 执行的机器指令(函数体二进制代码)。
- 特点:只读,防止程序意外修改自身指令。
2. 常量区(.rodata)
- 存放内容 :字符串常量、
const修饰的全局/静态变量(通常)。 - 特点:只读,尝试写入会引发段错误(Segment Fault)。
3. 数据段(.data)
- 存放内容 :已初始化且初值不为0 的全局变量和静态局部变量(包括
static修饰的变量)。 - 特点:程序启动时从可执行文件加载,生命周期贯穿整个程序运行。
4. BSS段(.bss)
- 存放内容 :未初始化或初值为0的全局变量和静态局部变量。
- 特点:不占用可执行文件磁盘空间,程序启动时由操作系统统一清零初始化。
5. 堆(Heap)
- 存放内容 :通过
malloc(C)/new(C++)动态分配的内存。 - 特点 :由低地址向高地址增长,需要手动释放(
free/delete,或 C++ 智能指针自动管理)。
6. 内存映射段(Memory Mapping Segment)
- 存放内容 :动态链接库(
.so/.dll)、mmap映射的大文件或匿名内存。 - 特点:位于堆和栈之间,用于文件映射和线程间共享内存。
7. 栈(Stack)
- 存放内容:局部变量、函数参数、返回地址、栈帧(Frame)信息。
- 特点:由高地址向低地址增长,由编译器自动分配和释放,效率高但容量有限(通常几 MB)。
二、C/C++内存管理方式
I、C语言管理方式
在C语言中,通常使用malloc,calloc,realloc和free来管理内存;
首先我们要知道malloc,calloc,realloc三者之间的区别
malloc申请的空间是未初始化的,速度快;
calloc可以开空间并将申请的区域初始化为0;
realloc通常用于扩容,分为原地扩容 和异地扩容,异地扩容成功后会释放掉之前申请的空间,但是如果扩容失败就会导致内存泄露,因此要用临时指针进行接收
free就是释放掉我们申请的空间,对于申请的空间,一定要free
下面我们来看一道题目:
cpp
void Test()
{
int* p1 = (int*)calloc(4, sizeof(int));
int* p2 = (int*)realloc(p1, sizeof(int) * 10);
//需要free(p1)吗?
free(p2);
}
答案是不需要
如果p2是原地扩容,那么就会直接返回p1的地址,最终释放的是同一块地址,无需释放p1
如果p2是异地扩容,那么会将原空间内容拷贝到新空间,接着释自动释放掉原空间,并将p1置为空,无需手动free
||、C++管理方式
1、new和delete
由于C++是兼容C语言的,因此C语言的malloc,calloc,realloc, free在C++编译环境中依然能正常使用;
C++中通过new和delete操作符进行动态内存管理
下面通过代码来了解new和delete的使用方法
2、内置类型
cpp
int main()
{
//申请一个int类型空间
int* p1 = new int;
delete p1;
//申请一个int类型空间并初始化为1
int* p2 = new int(1);
delete p2;
//申请5个int类型的空间
int* p3 = new int[5];
delete[] p3;
//申请5个int类型的空间并初始化
int* p4 = new int[5] {1, 2, 3, 4, 5};
delete[] p4;
//申请10个int类型的空间并初始化
//后续没有被初始化的值均为0
int* p5 = new int[10] {1, 2, 3, 4, 5};
delete[] p5;
return 0;
}
注意:申请和释放单个元素的空间,使用new和delete操作符
申请和释放连续的空间,使用new和delete
必须要匹配使用申请空间和释放空间
3、自定义类型
对于自定义类型,new和delete最大的区别就是new会在申请完空间之后自动调用构造函数;
delete会先调用析构函数然后再释放空间;
cpp
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void Test02()
{
//对于自定义类型
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);//自动调用构造函数和析构函数
free(p1);
delete p2;
A* p3 = new A[10];
delete[] p3;
}
我们通过程序运行结果来看是否互自动调用构造函数和析构函数

和预想一样,确实自动调用了构造函数和析构函数;
这就是C++中自己的内存管理方式,我们继续向下看;
在C语言中,对于malloc申请的空间通常用临时指针来接收返回值判断是否申请失败;
而在C++中new申请之后没有返回值,要采用捕获异常的方式来判断,如下所示
cpp
//x86环境
int main()
{
try
{
// throw try/catch
//相当于申请1G
void* p1 = new char[1024 * 1024 * 1024];
cout << p1 << endl;
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
在上面程序中,我们想要在堆上申请2G空间,在x86环境下,看能否申请成功

程序打印出来了bad allocation,说明程序申请失败了,只申请了1G空间;
当前阶段,只需会捕获异常即可!
三、实现原理
new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,
new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间
- new/delete 操作符 :这是C++的关键字,是你在代码中直接使用的入口;它负责编排整个流程,但不直接进行内存分配。
- operator new/operator delete 函数 :这是C++标准库提供的全局函数 。
new操作符会调用它们来执行真正的内存分配和释放。可以把它看作是malloc/free的"C++风格包装器"。 - malloc/free 函数 :这是C标准库的函数,是底层内存管理的最终执行者。
operator new和operator delete在底层最终调用的就是它们。
a、对于普通对象
也就是说,当我们写下一个new T时:
1、编译器首先会分配内存,调用operator new(sizeof(T))函数,这个函数又会调用malloc,让其从堆上分配一块空间,如果分配失败,operator new会抛出一个std::bad_alloc异常,并不会像malloc那样返回NULL
2、接着会调用T的构造函数,完成对对象的初始化
当我们写下一个delete T时:
1、编译器首先会调用析构函数,释放对象内部申请的资源;
2、接着调用operator delete(void* p)这个函数,这个函数内部会调用free,将申请的空间进行释放
b、对于对象数组
new T[N]:
1、编译器首先会调用operator new[],实际上会调用operator new来申请内存
注意:编译器通常会多申请一块小空间,放在这个内存块前面,存储数组元素个数
2、在申请的内存,依次调用n次析构函数,初始化数组中的每个对象;
delete[] p:
1、首先会先读取数组元素个数,从p指向位置向前偏移,读取存储元素个数
2、根据n的大小来确定调用析构函数的次数,清理对象
3、最终调用operator delete[](内部会调用operator delete和free)释放整块内存(包括存储元素个数的部分)
特别注意 :
delete[]依赖这个存储的"元素个数"来知道要调用多少次析构函数;因此,用 new\[\] 分配的内存,必须用 delete\[\] 释放 ,否则可能导致只析构了第一个对象,造成内存泄漏或其他未定义行为。对于没有自定义析构函数的简单类型(如int),编译器可能不会存储这个计数。
四、定位new表达式
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:new(place_address)type或者new(place_address)type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:定位new表达式在实际使用中一般是配合内存池使用;因为内存池分配出来的内存没有初始化,那就需要 使用new的定义表达式显示调用构造函数
我们通过下面代码来体会显示调用构造函数
cpp
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
//此时p1指向的是一块分配好的空间,但并没有被初始化,因为没有执行构造函数
A* p1 = (A*)malloc(sizeof(A));
new(p1)A;//如果A类的构造函数有参数时,需要传参
p1->~A();//析构函数
//显示调用析构函数之后,可以直接使用free,不过建议配套使用
free(p1);
return 0;
}
先来看上面这段程序运行结果:

上述这个例子的构造函数是无参的,下面来观察带参构造函数
cpp
int main()
{
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);//显示调用构造函数
p2->Print();
p2->~A();//析构函数
operator delete (p2);//底层调用free
return 0;
}

完全没有问题!和预期结果一致
五、malloc/free与new/delete的区别
在C语言中,malloc,calloc,realloc,free管理内存的都是函数;
而C++中,new和delete是关键字
下面就来分析C/C++中malloc/free与new/delete的区别
首先来看共同特点:都是从堆上申请空间,并且需要手动释放!
不同点:
malloc和free是函数,而new和delete是操作符malloc申请的空间不会初始化,new可以初始化malloc申请的空时,需要手动计算空间大小并传递,new只需在后面跟上类型即可,如果是多个对象, 中指定对象大小即可malloc的返回值为void*,在使用时必须强转,new不需要,new后面就是空间类型malloc申请空间失败时,返回的是NULL,因此必须要判空处理;new不需要判空,需要捕获异常- 申请自定义类型时,
malloc/free只会申请/释放空间,不会调用构造函数和析构函数,而new在申请完空间后会调用构造函数完成对象的初始化,delete会在释放前调用析构函数清理对象资源,最终释放整块内存
如果觉得有帮助,可以关注Github项目持续更新