一、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);
}
//1. 选择题:
//选项 : A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
//
//globalVar在哪里?____ C 全局变量,未被 static 修饰但作用域是整个程序,存放在数据段(静态区)
//staticGlobalVar在哪里?____ C static 修饰的全局变量,作用域限制在当前文件,但存储位置仍在数据段
//staticVar在哪里?____ C 函数内 static 变量,生命周期全局,存储在数据段,仅作用域在函数内
//localVar在哪里?____ A 函数内普通局部变量,栈帧分配,函数结束后销毁
//num1 在哪里?____ A 函数内数组,属于局部变量,数组本身(存储空间)在栈上
//char2在哪里?____ A 函数内字符数组,数组名是栈上的地址,存储在栈
//* char2在哪里?___ A char2 是数组首地址,*char2 是数组第一个元素 'a',元素都存在栈上的数组空间里
//pChar3在哪里?____ A 函数内的指针变量本身(存储地址的变量),在栈上
//* pChar3在哪里?____ D 指针指向字符串常量 "abcd",常量存储在只读的代码段(常量区)
//ptr1在哪里?____ A 指针变量本身(存储 malloc 返回地址的变量),在栈上
//* ptr1在哪里?____ B ptr1 指向 malloc 申请的内存,动态内存分配在堆上

说明:
- 栈又叫堆栈------非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux)
- 堆用于程序运行时动态内存分配,堆是可以上增长的。
- 数据段------存储全局数据和静态数据。
- 代码段------可执行的代码/只读常量。
注意:calloc/realloc 和 malloc 一样,申请的内存都在堆区;
二、C语言中的动态内存管理方式:calloc/realloc/malloc/free
calloc / realloc / malloc的区别:
- malloc 主要用于分配指定字节大小的内存块 ,它仅完成内存分配操作 ,不会对分配的内存做任何初始化处理,内存中的内容是随机的垃圾值,使用时只需传入一个表示总字节数的参数即可,适合对性能要求高、无需初始化的小块内存分配场景。
- calloc 同样是分配内存 ,但它会在分配后自动将内存内容初始化为 0,安全性更高,使用时需要传入两个参数,分别是元素个数和单个元素的大小,更适合需要内存清零、对数据安全性有要求的场景。
- realloc 核心作用是调整已通过 malloc/calloc 分配的内存块大小 ,它的参数包含原内存指针和调整后的总大小,调整后的新增内存部分不会被初始化;当 realloc 执行时,若原内存块后方有足够连续空间,会直接扩展内存(原地扩容),若空间不足则会申请新的内存块、拷贝旧数据,并自动释放原内存块(异地扩容),若 realloc 调用失败会返回 NULL,此时原内存块依然有效,不会被释放。
malloc的实现原理?
三、C++内存管理方式
注意:C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理。
new/delete操作内置类型
cpp
void Test()
{
// 动态申请一个int类型的空间
int* ptr1 = new int;
int* ptr2 = new int[10];
delete ptr1;
delete[] ptr2;
// 支持初始化
int* ptr3 = new int(0);
int* ptr4 = new int[10]{1,2,3,4};
delete ptr3;
delete[] ptr4;
}
总结:
- new/delete是 C++ 面向对象的内存管理方式,支持初始化和构造 / 析构函数调用,malloc/free是 C 的底层内存函数,仅做内存分配释放;
- 使用规则:new配delete、new[]配delete[ ],不可混用,避免内存泄漏或析构不全;
- 内存分布:new/malloc分配的内存在堆区,指针变量本身(如 ptr4/ptr5/ptr6)在栈区。
new和delete操作自定义类型
cpp
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A; // 调用默认构造
A* p3 = new A(10);
A* p4 = new A[5];
free(p1);
delete p2;
delete p3;
delete[] p4;
return 0;
}
new/delete 与 malloc/free 核心区别
- malloc/free 仅做内存操作,不会调用A的构造函数,
- new/delete 完整管理对象生命周期 :
new A:先调用operator new(底层封装malloc)分配内存 ,再自动调用A的默认构造函数,将原始内存初始化为合法的A对象;new A(10):分配内存后,调用A的带参构造函数,完成对象初始化;
delete p2/delete p3:先调用A的析构函数(释放对象持有的资源),再调用operator delete(底层封装free)释放内存。
new [ ] / delete [ ] 的底层实现原理
- 当用new A[5]创建自定义类型数组时,new[]并非只分配5*sizeof(A)的内存,而是会在数组内存的 "头部" 额外开辟 4/8 字节(取决于系统位数)的空间,用于存储数组的元素个数(这里是 5)。new [ ] 会多开辟 "计数内存":[计数区(存储5)] + [A对象1] + [A对象2] + ... + [A对象5];返回的指针p4:指向第一个A对象的起始地址(而非计数区),对用户透明。
- delete [ ] 的执行逻辑(为何不能拆分成多个 delete):delete[ ] p4 执行时,会先根据p4回退到计数区,读取元素个数(5);从最后一个A对象开始,依次调用 5 次A的析构函数(保证每个对象的资源都被释放);最后释放包含计数区在内的全部内存(调用operator delete[ ])。
四、operator new与operator delete函数(重要点进行讲解)
operator new与operator delete函数(重点)
注意:new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new 在底层调用operator new全局函数来申请空间,delete 在底层通过operator delete全局函数来释放空间。
operator new 核心概念:
- 本质:C++ 提供的全局函数(也可类内重载),核心职责是仅分配堆内存,不涉及任何对象的构造逻辑。
- 函数原型(默认全局版):void* operator new(size_t size);
参数 size:需要分配的内存字节数;
返回值:成功返回指向分配内存的 void* 指针,失败默认抛出 std::bad_alloc 异常(而非像 malloc 那样返回 NULL)。 - 底层逻辑:默认实现是对 malloc 的封装,会循环尝试调用 malloc 申请内存;若 malloc 失败,会调用内存不足处理函数,仍失败则抛异常(异常章节学习)。
operator delete 核心概念:
- 本质:C++ 提供的全局函数(也可类内重载),核心职责是仅释放堆内存,不涉及任何对象的析构逻辑。
- 函数原型(默认全局版):void operator delete(void* ptr);
参数 ptr:需要释放的内存指针(若为 NULL,函数无操作);
无返回值。 - 底层逻辑:默认实现是对 free 的封装,释放前会做指针有效性校验,保证线程安全后调用 free 释放内存。
重载特性:
- 可在全局范围或类内重载 operator new/operator delete,实现自定义内存管理策略(如内存池、内存对齐、内存使用统计等);
- 类内重载时,该类的 new 操作会优先调用自定义版本,全局重载则影响所有未自定义的类。
代码示例:
cpp
#include <iostream>
#include <malloc.h> // malloc/free
#include <new> // bad_alloc
using namespace std;
// ===================== 1. 全局重载 operator new/operator delete =====================
// 自定义全局 operator new:封装 malloc,添加分配日志
void* operator new(size_t size)
{
cout << "[全局 operator new] 申请 " << size << " 字节内存" << endl;
void* ptr = malloc(size); // 底层调用 malloc
if (!ptr)
{ // 模拟内存不足场景
throw bad_alloc(); // 失败抛异常
}
return ptr;
}
// 自定义全局 operator delete:封装 free,添加释放日志
void operator delete(void* ptr) noexcept
{
cout << "[全局 operator delete] 释放内存:" << ptr << endl;
if (ptr)
{
free(ptr); // 底层调用 free
}
}
// ===================== 2. 类内重载 operator new/operator delete =====================
class MyClass
{
public:
int num;
// 类内重载 operator new:仅为该类分配内存,添加专属日志
void* operator new(size_t size)
{
cout << "[MyClass operator new] 为MyClass申请 " << size << " 字节内存" << endl;
void* ptr = malloc(size);
if (!ptr)
{
throw bad_alloc();
}
return ptr;
}
// 类内重载 operator delete:仅释放该类的内存
void operator delete(void* ptr) noexcept
{
cout << "[MyClass operator delete] 释放MyClass内存:" << ptr << endl;
if (ptr)
{
free(ptr);
}
}
// 构造函数(仅初始化,不涉及内存分配)
MyClass(int n = 0) : num(n)
{
cout << "MyClass 构造函数:num = " << num << endl;
}
// 析构函数(仅清理,不涉及内存释放)
~MyClass()
{
cout << "MyClass 析构函数:num = " << num << endl;
}
};
// ===================== 3. 测试逻辑 =====================
int main()
{
try
{
// 测试1:基础类型(使用全局重载的 operator new/delete)
int* p1 = new int(10); // new运算符:调用全局operator new + 初始化
cout << "p1 指向的值:" << *p1 << endl;
delete p1; // delete运算符:调用全局operator delete
cout << "------------------------" << endl;
// 测试2:自定义类(使用类内重载的 operator new/delete)
MyClass* p2 = new MyClass(20); // new运算符:调用MyClass::operator new + 构造函数
cout << "p2->num:" << p2->num << endl;
delete p2; // delete运算符:调用析构函数 + MyClass::operator delete
}
catch (const bad_alloc& e)
{
// 捕获内存分配失败的异常
cerr << "内存分配失败:" << e.what() << endl;
return 1;
}
return 0;
}
总结:通过上述两个全局函数的实现知道,operatornew实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operatordelete最终是通过free来释放空间的。
五、 定位new表达式(placement-new)
定位 new(placement new)是一种特殊的 new 表达式,它不在堆上申请新的内存,而是在一块已经分配好的、原始的内存地址上,直接调用构造函数来初始化一个对象。
核心作用 :在已分配的内存空间中 "就地" 构造对象,常用于内存池、自定义内存管理等场景。
使用格式:
cpp
new (place_address) type;
new (place_address) type(initializer-list);
//place_address:必须是一个合法的指针,指向一块大小足够、且未被使用的内存。
//type:要构造的对象类型。
//initializer-list:对象的初始化参数,用于调用对应的构造函数。
注意:通过定位 new 构造的对象,不能直接用 delete 释放,必须显式调用其析构函数,然后再释放原始内存。
代码示例:
cpp
class A {
public:
A(int a = 0) : _a(a) {
cout << "A() 构造函数: this = " << this << ", _a = " << _a << endl;
}
~A() {
cout << "~A() 析构函数: this = " << this << ", _a = " << _a << endl;
}
int GetA() const { return _a; }
private:
int _a;
};
int main()
{
// 场景1:在 malloc 分配的内存上使用定位 new
cout << "=== 场景1:在 malloc 分配的内存上使用定位 new ===" << endl;
// 1. 先分配原始内存
A* p1 = (A*)malloc(sizeof(A));
if (p1 == nullptr)
{
cerr << "malloc 失败" << endl;
return 1;
}
cout << "malloc 分配的内存地址: " << p1 << endl;
// 2. 使用定位 new 在 p1 指向的内存上构造对象 A(10)
new (p1) A(10);
cout << "对象 p1 的值: " << p1->GetA() << endl;
// 3. 显式调用析构函数,清理对象
p1->~A();
// 4. 释放原始内存
free(p1);
cout << "\n=== 场景2:在 operator new 分配的内存上使用定位 new ===" << endl;
// 1. 先通过 operator new 分配原始内存
A* p2 = (A*)operator new(sizeof(A));
cout << "operator new 分配的内存地址: " << p2 << endl;
// 2. 使用定位 new 在 p2 指向的内存上构造对象 A(20)
new (p2) A(20);
cout << "对象 p2 的值: " << p2->GetA() << endl;
// 3. 显式调用析构函数
p2->~A();
// 4. 释放原始内存
operator delete(p2);
return 0;
}
六、malloc / free和new / delete的区别
malloc / free和new / delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放。