跳到主要内容
C/C++ 内存分布深度剖析:从代码区到堆栈,new/delete 与 malloc/free 详解 | 极客日志
C++ 算法
C/C++ 内存分布深度剖析:从代码区到堆栈,new/delete 与 malloc/free 详解 综述由AI生成 深入剖析 C/C++ 程序内存分布,涵盖代码段、数据段、堆、栈等区域。对比了 C 语言 malloc/calloc/realloc/free 与 C++ new/delete 操作符的区别,详细讲解了 operator new/delete 底层实现、定位 new 表达式以及内存分配策略(如空闲链表、分箱)。旨在帮助开发者理解内存管理机制,避免泄漏与野指针,提升程序性能与稳定性。
二进制 发布于 2026/3/30 更新于 2026/5/22 24 浏览C/C++ 内存分布深度剖析:从代码区到堆栈,new/delete 与 malloc/free 详解
前言
在 C/C++ 程序的世界中,内存管理是开发者必须掌握的核心技能之一。从代码区的静态指令到堆栈的动态分配,从 C 语言的 malloc/free 到 C++ 的 new/delete,内存的布局与管理方式直接影响程序的性能、稳定性与安全性。理解这些机制不仅能帮助开发者编写高效的代码,还能避免内存泄漏、野指针等常见问题。
本文将系统剖析 C/C++ 内存分布的各个区域,对比 C 语言与 C++ 在动态内存管理上的异同,深入探讨 new/delete 的实现原理及其与 malloc/free 的本质区别。同时,还会介绍 operator new、定位 new 表达式等高级特性,为读者呈现一套完整的内存管理知识体系。
1. C/C++ 内存分布
程序的内存布局显示了其数据在执行过程中如何存储在内存中。它帮助开发者高效理解和管理内存。
内存被划分为代码、数据、堆和栈等部分。
了解内存布局有助于优化性能、调试以及防止分段错误和内存泄漏等错误。
1.1 栈 (Stack)
栈又叫堆栈 – 非静态局部变量/函数参数/返回值等等,栈是向下增长的。
存储内容 :
├── 局部变量(非 static)
├── 函数参数
├── 函数返回地址
├── 函数调用的上下文信息(寄存器状态等)
└── 局部常量(const 局部变量)
特点 :
├── 编译器自动管理(自动分配和释放)
├── 从高地址向低地址增长 ↓
├── 函数结束时自动释放
└── 空间有限(通常 1-8MB)
void func (int param) {
int local = 10 ;
const int LOCAL_CONST = 5 ;
char buffer[100 ];
}
1.2 内存映射段 (Memory Mapped Segment)
内存映射段是高效的 I/O 映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
定义 :通过 mmap() 系统调用映射的内存区域
存储内容 :
├── 共享库
├── 内存映射文件
├── 共享内存
├── 匿名映射(大块内存分配)
└── 动态链接库加载区域
特点 :
├── 动态创建,不在可执行文件中预定义
├── 可以与文件关联,也可以是匿名的
├── 可以设置共享或私有
├── 大块内存分配时 malloc 可能使用 mmap
└── 生命周期由程序员控制
int fd = open("file.txt" , O_RDWR);
void * p = mmap(NULL , size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );
int shmid = shmget(key, size, IPC_CREAT | 0666 );
void * p = shmat(shmid, NULL , 0 );
void * big = malloc (128 * 1024 );
1.3 堆 (Heap)
存储内容 :
├── malloc / calloc / realloc 分配的内存
├── new / new[] 分配的内存
└── 动态创建的对象
特点 :
├── 程序员手动管理(分配和释放)
├── 从低地址向高地址增长 ↑
├── 生命周期由程序员控制
└── 不释放会导致内存泄漏
int * p = (int *)malloc (sizeof (int ) * 10 );
int * arr = new int [100 ];
free (p);
delete[] arr;
1.4 数据段 (Data Segment)
存储内容 :
├── 已初始化的全局变量
└── 已初始化的静态变量(static)
特点 :可读写,程序启动时加载
int global_var = 100 ;
static int static_var = 50 ;
1.5 代码段 (Code Segment)
存储内容 :
├── 程序的可执行机器指令(编译后的代码)
├── 字符串常量(如 "Hello World")
└── 全局常量(const 全局变量)
特点 :只读、可被多个进程共享
const int MAX_SIZE = 100 ;
char * str = "Hello" ;
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 在哪里?_____
答案 :
C C C A A A A A D A B
主要讲一下后六个空 :
abcd\0 为代码存在代码段中,char2 在栈上开了 5 个字节的数组并把 abcd\0 拷贝过来,char2* 指向数组的首元素地址,即 char2 和 char2* 都在栈上;pChar3 为局部变量存在栈上,*pChar3 指向 abcd\0 字符串,故在代码段上;ptr1 也是栈上的局部变量,*ptr1 指向堆上开的空间,则 ptr 在栈上,*ptr 在堆上。
globalVar 为全局变量在数据段
staticGlobalVar 为静态全局变量在静态区
staticVar 为静态局部变量在静态区
localVar 为局部变量在栈
num1 为局部变量在栈
2. C 语言中动态内存管理方式:malloc/calloc/realloc/free void Test () {
int * p2 = (int *)calloc (4 , sizeof (int ));
int * p3 = (int *)realloc (p2, sizeof (int ) * 10 );
free (p3);
}
malloc/calloc/realloc 的区别?
三者都是 C 标准库的堆内存分配函数,核心区别在于:malloc 只分配不初始化,calloc 分配并清零,realloc 用于调整已有内存大小 。
calloc 相当于 malloc + memset(),但性能略低,适合需要初始值为 0 的场景,比如初始化数组或结构体。
realloc 有一个常见陷阱,它可能返回新地址,如果内存不够用,它会在别处重新分配并把数据拷过去。所以不能用原指针接收返回值 ,否则失败返回 NULL,原指针就丢了,造成内存泄漏。
realloc(NULL, size) 等价于 malloc(size),这是标准保证的行为;
calloc 的清零在某些系统上会利用 OS 已清零的页,不一定比 malloc + memset 慢;
实际项目中更推荐封装一层,统一做 NULL 检查,避免忘记处理分配失败。
在现代 C++ 项目里我们一般使用 new/delete 或智能指针,但在嵌入式、性能敏感或纯 C 场景下,这三个函数还是很有用的,理解它们的行为差异对排查内存问题很重要。
malloc 的核心是向操作系统申请一大块内存,然后自己管理这块内存的分配与回收,尽量减少系统调用次数。
malloc 底层靠两个系统调用向 OS 要内存:
系统调用 用途 特点 brk / sbrk移动堆顶指针,扩展堆 适合小内存,连续分配 mmap直接映射一块匿名内存 适合大内存(通常 > 128KB)
小内存用 brk 扩堆,大内存直接 mmap,这样大块内存 free 之后可以直接还给 OS,不会一直占着。
malloc 不是每次都问 OS 要内存,而是维护一个空闲块链表 ,free 掉的内存先留着复用:
堆内存布局: [ chunk1 | chunk2(free ) | chunk3 | chunk4(free ) | ... ]
↓ ↓
free list ←——————————————→ free list
每个内存块前面有一个隐藏的 header ,记录元数据:
struct chunk {
size_t size;
int is_free;
struct chunk * next;
};
调用 malloc(n) 拿到的指针,其实指向 header 后面,free(p) 时会往前偏移读取 header,这就是为什么 free 不需要传大小。
面试官可能追问'怎么从空闲链表里找块',常见策略:
策略 描述 特点 First Fit 找第一个够大的块 快,但碎片多 Best Fit 找最接近大小的块 碎片少,但慢 Worst Fit 找最大的块切割 剩余块更大,适合大量小分配
glibc 的 ptmalloc 用的是分箱(Bins)策略 ,按大小分类管理:
small bins → 固定大小的小块(< 512 B),精确匹配
large bins → 大块,范围匹配
unsorted bin → 刚 free 的块先放这里,延迟归类
fast bins → 极小块的缓存,不合并,极速复用
按大小分箱,找对应的 bin 去拿,比遍历链表快很多。
free (B) 之后: [ A(used) | B(free ) | C(free ) ] → [ A(used) | B+C(free ) ]
相邻的空闲块会合并,防止碎片化。glibc 用 footer 记录前一块状态,O(1) 完成合并。
glibc 的 ptmalloc 给每个线程分配独立的 arena(内存池),减少锁竞争。这也是 tcmalloc、jemalloc 这些替代品的优化核心——更细粒度的线程本地缓存。
glibc 默认的 ptmalloc 在多线程高并发下性能一般,所以很多高性能项目会替换成 tcmalloc (Google)或 jemalloc (Facebook),它们在线程本地缓存和碎片控制上做了更多优化。
OS 怎么给内存 → malloc 怎么管理 → 怎么找空闲块 → 碎片怎么处理 → 多线程怎么办
(brk/mmap) (空闲链表+header) (分箱策略) (合并) (arena/tcmalloc)
3. C++ 内存管理方式 C 语言内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理 。
C++ 中通过 new 和 delete 操作符进行动态内存管理。
new 负责申请内存,new 操作符返回的是申请到的内存空间的起始地址,需要指针存放。
new 申请一个变量的空间,new[] 申请一个数组的空间
delete 负责释放(回收)内存
delete 负责释放一个变量的空间,delete[] 释放一个数组的空间
new 和 delete 配对,new[] 和 delete[] 配对使用
3.1 new/delete 操作内置类型 void Test () {
int * ptr1 = new int ;
int * ptr2 = new int (10 );
int * ptr3 = new int [10 ];
int * ptr4 = new int [10 ]{0 };
int * ptr5 = new int [10 ]{1 , 2 , 3 , 4 , 5 };
delete ptr1;
delete ptr2;
delete [] ptr3;
delete [] ptr4;
delete [] ptr5;
}
注意:申请和释放单个元素的空间,使用 new 和 delete 操作符,申请和释放连续的空间,使用 new[] 和 delete[],注意:匹配起来使用。
3.2 new 和 delete 操作自定义类型 #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* p1 = (A*)malloc (sizeof (A));
A* p2 = new A (1 );
free (p1);
delete p2;
int * p3 = (int *)malloc (sizeof (int ));
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 会调用析构函数,而 malloc 与 free 不会。
#include <iostream>
using namespace std;
class A {
public :
A (int a1 = 0 , int a2 = 0 ) : _a1(a1), _a2(a2) { cout << "A(int a1 = 0, int a2 = 0)" << endl; }
A (const A& aa) : _a1(aa._a1) { cout << "A(const A& aa)" << endl; }
A& operator =(const A& aa) { cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) { _a1 = aa._a1; }
return *this ;
}
~A () { cout << "~A()" << endl; }
void Print () { cout << "A::Print->" << _a1 << endl; }
A& operator ++() { _a1 += 100 ; return *this ; }
private :
int _a1 = 1 ;
int _a2 = 1 ;
};
int main () {
A* p1 = new A (1 );
A* p2 = new A (2 , 2 );
A aa1 (1 , 1 ) ;
A aa2 (2 , 2 ) ;
A aa3 (3 , 3 ) ;
A* p3 = new A[3 ]{aa1, aa2, aa3};
A* p4 = new A[3 ]{A (1 , 1 ), A (2 , 2 ), A (3 , 3 )};
A* p5 = new A[3 ]{{1 , 1 }, {2 , 2 }, {3 , 3 }};
return 0 ;
}
4. operator new 与 operator delete 函数
4.1 operator new 与 operator delete 函数 new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new 在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete 全局函数来释放空间。
void * __CRTDECL operator new (size_t size) _THROW1 (_STD bad_alloc) {
void * p;
while ((p = malloc (size)) == 0 )
if (_callnewh(size) == 0 ) {
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
void operator delete (void * pUserData) {
_CrtMemBlockHeader* pHead;
RTCCALLBACK (_RTC_Free_hook, (pUserData, 0 ));
if (pUserData == NULL ) return ;
_mlock(_HEAP_LOCK);
__TRY
pHead = pHdr (pUserData);
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY _munlock(_HEAP_LOCK);
__END_TRY_FINALLY
return ;
}
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
通过上述两个全局函数的实现知道,operator new 实际也是通过 malloc 来申请空间 ,如果 malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过 free 来释放空间的 。
5. new 和 delete 的实现原理
5.1 内置类型 如果申请的是内置类型的空间,new 和 malloc,delete 和 free 基本类似,不同的地方是:new/delete 申请和释放的是单个元素的空间,new[] 和 delete[] 申请的是连续空间,而且 new 在申请空间失败时会抛异常,malloc 会返回 NULL。
5.2 自定义类型
调用 operator new 函数申请空间;
在申请的空间上执行构造函数,完成对象的构造。
在空间上执行析构函数,完成对象中资源的清理工作;
调用 operator delete 函数释放对象的空间。
调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请;
在申请的空间上执行 N 次构造函数。
在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理;
调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间。
6. 定位 new 表达式 (placement-new) 定位 new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type 或者 new (place_address) type(initializer-list)
place_address 必须是一个指针,initializer-list 是类型的初始化列表
使用场景:
定位 new 表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用 new 的定义表达式进行显示调构造函数进行初始化。
#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* p1 = (A*)malloc (sizeof (A));
new (p1) A;
p1->~A ();
free (p1);
A* p2 = (A*)operator new (sizeof (A));
new (p2) A (10 );
p2->~A ();
operator delete (p2) ;
return 0 ;
}
7. 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 在释放空间前会调用析构函数完成空间中资源的清理释放 。
结语 深入理解 C/C++ 内存分布和动态内存管理机制是编写高效、安全程序的关键基础。从代码区、静态存储区到堆栈的布局,再到 malloc/free 与 new/delete 的底层实现差异,每个环节都直接影响程序的性能和稳定性。
C 语言通过 malloc/calloc/realloc/free 提供灵活的手动内存管理,而 C++ 的 new/delete 不仅实现了内存分配释放,还融合了构造函数和析构函数的调用,为面向对象编程提供完整支持。operator new/delete 的底层机制、定位 new 表达式等高级特性,进一步扩展了内存控制的精细度。
掌握这些知识能帮助开发者避免内存泄漏、野指针等常见问题,同时在性能优化和复杂系统设计中做出明智选择。理解不同内存管理方式的适用场景,是每位 C/C++ 程序员从入门到精通的必经之路。
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online