进程内存分布
典型的 Linux
进程内存分布图,图片来自这里:
文章图片
这张图中有映射段的位置,但是还有一个重要的部分的缺失,就是运行时的参数和环境变量,在 Linux/Unix 系统编程手册这本书第 6 章讲进程的内存分配里有给:
文章图片
malloc
内存分配在映射段
当 malloc
申请分配的内存过大(128K
以上),内部将使用 mmap
而不是 brk
来分配内存。
测试代码:
printf("program break:%p\n", sbrk(0));
char* ptr[10];
for(int i = 0;
i < 10;
++i)
{
ptr[i] = (char* )malloc(200 * 1024);
printf("malloc:%p program_break_%d:%p\n", ptr[i], i, sbrk(0));
}printf("stack:%p %p\n", &ptr, ptr);
while(1)
{
sleep(3);
}
输出如下图:
文章图片
显而易见的几点:
- 程序
program break
的位置没变,说明分配的内存不在堆上;而且malloc
分配的地址与program break
的地址差异巨大,应该在不同的区。 - 不在堆上,而且离栈相对较近,且此时
malloc
分配的内存在栈的下方。
/proc/pid/maps
目录下,获取动态库的映射地址。【Linux|malloc 内存分配位置及进程内存布局】
文章图片
关于 maps 里同一个动态库映射多次的原因的解释看这里。
用 python 快速的给分配出的内存地址和这里的 maps 的动态库地址作个排序:
a = [
('0x7f423e4ad000', "/usr/lib64/libc-2.28.so"),
('0x7f423e666000', "/usr/lib64/libc-2.28.so"),
('0x7f423e865000', "/usr/lib64/libc-2.28.so"),
('0x7f423e869000', "/usr/lib64/libc-2.28.so"),
('0x7f423e86f000', "/usr/lib64/ld-2.28.so"),
('0x7f423ea97000', "/usr/lib64/ld-2.28.so"),
('0x7f423ea98000', "/usr/lib64/ld-2.28.so")] #动态库的地址b = [
('0x7f423ea4c010', "program_break_0:0x2482000"),
('0x7f423ea19010', "program_break_1:0x2482000"),
('0x7f423e9e6010', "program_break_2:0x2482000"),
('0x7f423e9b3010', "program_break_3:0x2482000"),
('0x7f423e980010', "program_break_4:0x2482000"),
('0x7f423e94d010', "program_break_5:0x2482000"),
('0x7f423e91a010', "program_break_6:0x2482000"),
('0x7f423e8e7010', "program_break_7:0x2482000"),
('0x7f423e8b4010', "program_break_8:0x2482000"),
('0x7f423e47a010', "program_break_9:0x2482000")] #malloc 内存分配的地址c = a + b
c.sort(key = lambda x : int(x[0], base = 16))
地址升序排列:
文章图片
可以看到
malloc
分配的内存区域与动态库的地址是交织在一起的。Linux/Unix 系统编程手册 49.7 章节有描述:glibc 中的 malloc()实现使用 MAP_PRIVATE 匿名映射来分配大小大于 MMAP_符合这里分配
THRESHOLD 字节的内存块;MMAP_THRESHOLD 在默认情况下是 128 kB
200k
时,直接将内存分配在动态区。malloc 内存分配在堆上
如果分配的内存是100k(100 * 1024),程序的输出如下,可以看到 program break 的位置有变化,说明是在堆上分配的内存。
program break:0x663000两次分配内存之间的地址差值是 102416(0x65b910 - 0x642900 = 102416 = 100k + 16),100k 返回给 malloc 的调用者,多余的 16 字节用来保存长度相关信息。
malloc:0x642900 program_break_0:0x663000
malloc:0x65b910 program_break_1:0x695000
malloc:0x674920 program_break_2:0x695000
malloc:0x68d930 program_break_3:0x6c7000
malloc:0x6a6940 program_break_4:0x6c7000
malloc:0x6bf950 program_break_5:0x6f9000
malloc:0x6d8960 program_break_6:0x6f9000
malloc:0x6f1970 program_break_7:0x72b000
malloc:0x70a980 program_break_8:0x72b000
malloc:0x723990 program_break_9:0x75d000
stack:0x7fff79915ac0 0x7fff79915ac0
总结
- 当调用者请求的内存超过临界值,
malloc
分配的内存是进程的映射区,和动态库的内存占用相同区域;小于临界值时,分配的内存才是在堆上。 - 当
malloc
内存分配在堆上时,实际会多分配几个字节(位于实际返回给调用者的地址之前),用来记录分配的内存的长度;这样free
才能正确的释放内存。 - 即便返回的内存是在堆上,
malloc
也可能并未调用brk
来改变program break
的位置:其一是因为program break
下边本来就有部分未使用的空闲内存(malloc
调用brk
会超额分配内存,多余的至于堆顶);其二在于free
所释放的内存保存在空闲内存列表里,malloc
会首先检查这个列表中是否有合适的内存块(2种策略 first-fit 和 best-fit),之后才会去调用brk
分配更多的堆内存。
推荐阅读
- #|2017年蓝桥杯省赛-承压计算
- 大话设计模式|大话设计模式 —— 第二章《策略模式》C++ 代码实现
- 蓝桥杯|第十届蓝桥杯省赛编程题题解(C++A组)
- 蓝桥杯|第八届蓝桥杯省赛代码+题解
- linux|nacos源码修改编译(亲测)
- 算法|蓝桥 小明的游戏1 博弈论 nim
- #|力扣-105题 从前序与中序遍历序列构造二叉树(C++)- dfs
- C++|C++-析构函数
- 麦克算法|第12节课 图