最近学习C语言,对变量的储存位置很不清楚,后来通过多次查询资料有了自己的见解,特写此博客分享自己对C语言在内存中的储存过程的理解,如有错误望指正。
首先要知道,我们在应用程序中所使用的地址均是虚拟内存地址,在32位操作系统中,每一个进程所占用的虚拟内存是4G,4G的虚拟内存又分为1G的内核空间和3G的用户空间,内核空间是当前主机中所有进程共有的,用户空间是当前进程私有的,下面讲解的内存区域的划分,指的是用户空间的划分。
在了解了虚拟内存的基础上,我们继续看用户空间的区域划分。内存主要分为以下五个区域: 1、栈区(又称堆栈区) 存放函数的参数值、非静态局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限。 2、堆区 就是通过malloc动态分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放,如果不在程序中释放则一般整个程序结束后会被系统回收内存资源。分配方式类似于数据结构中的链表。“内存泄漏”通常说的就是堆区。 3、静态区(又称静态全局区) 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,所以静态区又分为两个段(data、bss后文详细解释)。程序结束后,由系统释放。 4、代码区 存放代码的区域 5、文字常量区 存放只读数据,如常量字符串
全局变量 -----> 静态区 静态变量 -----> 静态区 非静态局部变量、形参、返回值 -----> 栈区 动态申请的变量 -----> 堆区 代码 -----> 代码区 常量字符串 -----> 文字常量区
源文件在编译之后生成的.o文件已经将程序进行了分段,主要有以下四个段: 1、代码段 .text text段是程序代码段,它是由编译器在编译连接时自动计算的,当你在链接定位文件中将该符号放置在代码段后,那么该符号表示的值就是代码段大小,编译连接时,该符号所代表的值会自动代入到源程序中。 2、数据段(细分为两个段) ①已初始化段(又称全局初始化段、静态初始化段) .data data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小在编译连接时自动分配,它和你的程序大小没有关系,但和程序使用到的全局变量,常量数量相关。 ②未初始化段(又称全局未初始化段、静态未初始化段) .bss 通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。bss段属于静态内存分配。它的初始值也是由用户自己定义的连接定位文件所确定,用户应该将它定义在可读写的ram区内,源程序中使用malloc分配的内存就是这一块,它不是根据data大小确定,主要由程序中同时分配内存最大值所确定,不过如果超出了范围,也就是分配失败,可以等空间释放之后再分配。 3、只读数据段 .rodata 程序中定义的常量字符串等 4、堆栈段 栈主要保存函数的局部变量和参数以及返回值。是一种“后进先出”(last in first out,lifo)的数据结构,在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低 堆保存函数内部动态分配内存,堆是“先进先出”(first in first out,fifo)数据结构。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。
未初始化全局变量 -----> 数据段—> 未初始化段 .bss 未初始化静态变量 -----> 数据段—> 未初始化段 .bss 初始化全局变量 -----> 数据段—> 初始化段 .data 初始化静态变量 -----> 数据段—> 初始化段 .data 代码 -----> 代码段 常量字符串 -----> 只读数据段 .rodata 非静态局部变量、形参、返回值 -----> 不存在,程序运行时才会在栈区产生 动态申请的变量 -----> 不存在,程序运行时才会在堆区产生
为了验证上述结果,我们写一下程序测试:
#include <stdio.h> int a; int b = 1; static int c; static int d = 2; int main(int argc, char *argv[]) { int e; int f = 3; static int g; static int h = 4; char i[] = "hello"; //打印每个变量的值 printf("a=%d\n",a); printf("b=%d\n",b); printf("c=%d\n",c); printf("d=%d\n",d); printf("e=%d\n",e); printf("f=%d\n",f); printf("g=%d\n",g); printf("h=%d\n",h); printf("i=%s\n",i); //打印每个变量的地址 printf("a=%p\n",&a); printf("b=%p\n",&b); printf("c=%p\n",&c); printf("d=%p\n",&d); printf("e=%p\n",&e); printf("f=%p\n",&f); printf("g=%p\n",&g); printf("h=%p\n",&h); printf("i=%p\n",i); return 0; }编译(不链接,链接会产生更多的数据,不便验证): #gcc -c test.o test.c 查看数据分段情况: #objdump -D test.o
编译链接并运行查看打印地址情况: #gcc -o test tset.c #./test
内存分区及数据段分配图
