一、内存分布图
直接上图分析
向下增长的栈:栈顶指针(SP)从高地址向低地址移动;
向上增长的栈:栈顶指针(SP)从底地址向高地址移动。
验证代码(linux平台下测试,这个栈的生长方向不同平台会有些差异,感兴趣的话可自行测试~):
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>int unInitialG;
static int sUnInitialG;
int initialG = 0;
static int sInitialG = 10;
const int cG = 0;void testStackIncreaseDirection(){int a;int b;printf("stack testStackIncreaseDirection allocation (stack): %p\n", &a);printf("stack testStackIncreaseDirection allocation (stack): %p\n", &b);
}int main() {int a = 0;int b = 0;int c = 0;void* heap_ptr = malloc(1024); // 通过 malloc 分配(通常在堆)void* mmap_ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // mmap 分配(通常在映射区域)printf("stack hight allocation (stack): %p\n", &a);printf("stack middle allocation (stack): %p\n", &b);printf("stack slow allocation (stack): %p\n", &c);testStackIncreaseDirection();printf("Mmap hight allocation (mmap): %p\n", mmap_ptr);printf("Heap slow allocation (malloc): %p\n", heap_ptr);printf("bss1 allocation (.bss): %p\n", &sUnInitialG);printf("bss2 allocation (.bss): %p\n", &unInitialG);printf("data1 allocation (.data): %p\n", &initialG);printf("data2 allocation (.data): %p\n", &sInitialG);printf("data3 allocation (.data): %p\n", &cG);free(heap_ptr); // 释放 malloc 分配的内存munmap(mmap_ptr, 4096); // 释放 mmap 分配的内存return 0;
}
结果图如下:
注意:在一个函数里面的局部变量,比如void testStackIncreaseDirection()函数,先定义的后压栈。
二、C++的内存怎么分配?
简洁版
- 全局变量,全局静态变量未初始化的存放在(.bss);
- 全局变量,全局静态变量初始化的存放在(.data);
- 函数参数、局部变量和函数返回值存放在栈区(stack);
- malloc和new申请的内存存放在堆区(heap),详细区别如下所示;
- 可执行文件存放在代码区(text)。
详细版
(1)代码区(Code Segment / Text Segment)
- 存放程序的 可执行代码(即编译后的指令)。
- 只读(防止程序意外修改自身代码)。
- 共享(多个进程可共享相同的代码段)。
(2)全局/静态区(Global & Static Segment)
- 存放 全局变量 和 静态变量(static 变量)。
- 变量的生命周期贯穿整个程序运行期(直到进程结束)。
- 初始化的变量存放在
.data
段,未初始化的存放在.bss
段。
(3)栈区(Stack Segment)
- 由编译器自动分配释放,用于存储局部变量、函数参数、返回地址等。
- 栈是 后进先出(LIFO) 结构,每进入一个函数,就会在栈上分配一块新的内存,函数执行完毕后自动释放。
特点
- 速度快(直接由 CPU 指令管理)。
- 不需要手动管理内存。
- 受限于栈空间大小(通常 1~8MB),递归太深可能导致栈溢出(Stack Overflow)。
(4)堆区(Heap Segment)
- 由程序员手动管理(使用
new
/delete
或malloc
/free
)。 - 适合大对象或动态分配的对象(大小和生存期不确定)。
- 堆区通常比栈大,但分配/释放效率比栈低。
特点
- 适用于大对象、动态数组、类对象。
- 程序员必须手动管理,否则可能会导致内存泄漏。
(5)常量区(Readonly Data Segment)
- 存储 字符串字面量 和
const
修饰的全局变量。 - 该区域的内容通常不可修改(否则可能会导致段错误 Segmentation Fault)。
三、栈和堆的对比
对比项 | 栈(Stack) | 堆(Heap) |
---|---|---|
分配方式 | 由编译器自动分配 | 由程序员手动分配 (new / malloc ) |
释放方式 | 由编译器自动释放 | 需要手动释放 (delete / free ) |
访问速度 | 快(LIFO 结构,CPU 指令直接管理) | 慢(自由分配,可能导致碎片化) |
生命周期 | 随函数调用自动管理 | 需要程序员手动管理 |
大小 | 较小(一般 1MB~8MB) | 较大(可动态扩展) |
典型应用 | 局部变量、函数参数、返回地址 | 动态数组、对象、大型数据结构 |
四、malloc
vs new
的核心区别
区别点 | malloc | new |
---|---|---|
函数 / 关键字 | C 标准库函数 | C++ 关键字 |
返回类型 | void* (需要强制转换) | 具体类型指针(自动转换) |
初始化 | 不会初始化(内存内容不确定) | 调用构造函数,自动初始化 |
内存分配方式 | 使用 heap manager 调用 sbrk() / mmap() | 使用 operator new(可能使用 malloc 底层分配) |
释放方式 | free(ptr) | delete ptr (调用析构函数) |
适用于 | C 语言和 C++ 低级内存操作 | C++ 面向对象编程(支持构造 / 析构函数) |
五、malloc
分配流程
调用 malloc(size)
时的底层步骤:
- 检查
size
:- 若
size < 128KB
,使用sbrk()
增长堆。 - 若
size ≥ 128KB
,使用mmap()
直接映射内存(调用mmap()
直接映射内存页)。
- 若
- 检查
free list
(空闲链表):- 若
free list
中有足够大的块,则复用空闲块。 - 若没有足够的空闲块,则调用
sbrk()
或mmap()
。
- 若
- 返回指针,供程序使用。
六、malloc/free
vs new/delete
的使用场景
使用场景 | 推荐方式 |
---|---|
C 语言 | malloc/free |
C++ 对象(类) | new/delete |
需要构造函数 / 析构函数 | new/delete |
简单数据结构(如 int[] ) | new/delete 或 std::vector |
手动内存管理,性能优化(如内存池) | malloc/free |
end!
有哪里不对的欢迎指出,批评指正,谢谢。
制作不易,麻烦观众老爷点个赞,再走呗,鼓励一下,感谢!