内存布局
简单模型图:
内存分区 | 描述 |
---|---|
栈(stack,又称堆栈) | 由编译器自动分配和释放,存放函数的参数值、局部变量的值、函数的返回值等。形如数据结构中的栈,具有后进先出的特点。进程的栈空间位于进程用户空间的顶端,并向下扩展。每次函数调用都会在进程栈空间中开辟自己的栈空间,函数返回后,函数的栈空间消失,所以函数返回局部变量的地址是非法的。 |
堆 (heap) | 堆内存是在程序运行过程中分配,一般由程序员手动分配和释放, 若不释放,程序退出时会由操作系统回收。其大小并不固定,可动态扩张或缩减。 |
数据段(Data segment) | 含全局变量和静态变量。可大致分为未初始化数据段(.bss )和初始化数据段(.data ) |
代码段 (Text segment) | 存放 CPU 执行的机器指令。代码区是可以跨进程共享的,在内存中有一份代码即可。且通常也是只读的,以防止程序意外修改其执行。 |
这里,可对数据区再做一个更细致的划分
分区 | 描述 |
---|---|
.bss (Block Started by Symbol) |
用来存储未初始化的全局变量和静态变量。而在程序开始执行之前(也就是main()之前),内核会将这部分的数据初始化为0或一个空指针。 |
.data |
用于保存初始化的全局变量和静态静态变量。注意该区不是只读的,变量的值可以在运行时改变。 |
.rodata (read-only-data) |
只读数据段,存储const修饰的全局变量、诸如“Hello World”的字符串常量。编译器会去掉重复的字符串常量,程序的每个字符串常量只有一份,并且是多个进程间共享的,以提高空间利用率。注意,有些嵌入式系统中,rodata放在ROM里,运行时直接读取,而无需要加载到RAM内存 |
内存布局模型:
以Linux 32位系统内存布局为例,其实际内存布局结构如下图:
横向示意图:
对于x86架构的CPU,其栈顶的地址要比栈底低,模型图:
写一段C代码来验证上述结论
#include <stdio.h>
#include <stdlib.h>
int g_var1; // 未初始化的全局变量,存储在数据段的BSS区域,值默认为0
int g_var2 = 20; // 初始化的全局变量,存储在数据段的data区域,初始化值20
int main(int argc, char **argv) {
static int s_var1; // 未初始化的静态变量,存储在数据段的BSS区域,默认值为0
static int s_var2=10; // 初始化的静态变量,存储在数据段的data区域,初始化值10
// 初始化的局部变量,存储在栈中。"Hello "字符串常量被存储在RODATA区域中
char *str="Hello";
// 未初始化的局部变量,存储在栈中。其值是一个随机值,此时ptr称为 "野指针"
char *ptr;
// 从堆中分配100字节的内存空间,并将内存空间的第一个地址返回给ptr保存
ptr = malloc(100);
printf("[cmd args]: argv address: %p\n", argv);
printf("\n");
printf("[stack]: str address: %p\n", &str);
printf("[ stack]: ptr address: %p\n", &ptr);
printf("\n");
printf("[heap]: malloc address: %p\n", ptr);
printf("\n");
printf("[bss]: s_var1 address: %p value: %d\n", &s_var1, g_var1);
printf("[bss]: g_var1 address: %p value: %d\n", &g_var1, g_var1);
printf("[data]: g_var2 address: %p value: %d\n", &g_var2, g_var2);
printf("[data]: s_var2 address: %p value: %d\n", &s_var2, s_var2);
printf("[rodata]: \"%s\" address: %p \n", str, str);
printf("\n");
printf("[text]: main() address: %p\n", main);
printf("\n");
free(ptr);
return 0;
}
关注公众号:编程之路从0到1