11.执行阶段: 运行时内存模型
一个普通的C程序经过预处理器、编译器、汇编器和链接器后生成一个可执行的目标文件,它由最初的一段ASCII文本文件转化成为一个二进制文件,且这个二进制文件包含加载程序到内存并运行它所需的所有信息。
C运行时内存模型
进程在运行时为C程序提供了一个通用的运行时存储器映像。
Linux将这个运行时存储器映像组织成若干段的集合,它主要有两部分:进程虚拟存储器、内核虚拟存储器。进程虚拟存储器有我们熟悉的代码段、数据段、运行时堆、共享库段、用户栈。内核虚拟存储器包括内核中的代码和数据结构、与进程相关的数据结构。以32位系统的可执行文件的运行时存储器映像来说:
l 代码段总是从地址0x08048000处开始,它保存编译程序的机器代码
l data段在接下来的一个4KB对齐的地址处,保存已初始化的全局C变量和静态变量
l bss段记录的是未初始化的全局C变量,事实上它并不占据目标文件的任何空间,只是一个占位符
l 运行时堆在接下来的第一个4KB对齐的地址处,通过调用malloc库向上增长,用于程序的动态内存管理
l 共享库段,用于加载共享库、映射共享内存和文件I/O,使用mmap和unmap函数申请和释放新的内存区
l 用户栈占据进程地址空间的最高部分,并向下增长,用于存放调用过程中的局部变量、函数返回地址、参数
l 内核代码和数据、物理存储器,它们均被映射到所有进程共享的物理页面,这就为内核提供一个便利的方法来访问内存中任何特定的位置。对于每个进程来说他们均是一样的
l 最顶层的内核地址空间包括了与进程有关的数据结构,如页表、内核在进程的上下文结构task_struct和mm结构,内核栈
代码测试如下:
#include <stdio.h>
#include <stdlib.h>
int glob1=120;
int glob2;
extern int etext,edata,end;
int func2(){
intf2_local1,f2_local2;
printf("函数2的局部变量-\tf2_local1: %p \tf2_local2:%p\n",&f2_local1,&f2_local2);
}
int func1(){
intf1_local1,f1_local2;
printf("函数1的局部变量-\tf1_local1: %p \tf1_local2:%p\n",&f1_local1,&f1_local2);
func2();
}
int main(){
intm_local1,m_local2;
int*dyn_addr;
printf("代码段的结束地址:%p\n",&etext);
printf("初始化数据区的结束地址:%p\n",&edata);
printf("未初始化数据区的结束地址:%p\n",&end);
printf("运行时初始可用堆区域的边界地址:%p\n\n",(char *)sbrk(0));
printf("全局变量- glob1:%p \t glob2:%p\n",&glob1,&glob2);
dyn_addr=(int *)malloc(16);
printf("指针变量的值在堆区域-dyn_addr: %p\n",dyn_addr);
printf("函数地址-\t main:%p \tfunc1: %p \tfunc2:%p\n",main,func1,func2);
printf("main函数的局部变量-\tm_local1: %p \tm_local2:%p\n",&m_local1,&m_local2);
func1();
return 0;
}
输出如下:
结果看出这是一个运行在64位机器上的程序(64位的代码段总是从0x400000),函数地址main,func1,func2的地址均在代码区域中,全局变量glob1的地址在初始化数据区中,全glob2的地址在未初始化数据区中,指针遍历dyn_addr的值在堆区域中,局部变量m_local1、m_local2、f1_local1、f1_local2、f2_local1、f2_local2依次存放在栈(并且可知栈的增长方向是从高地址到低地址,堆的增长方向是从低地址到高地址)
一个普通的C程序经过预处理器、编译器、汇编器和链接器后生成一个可执行的目标文件,它由最初的一段ASCII文本文件转化成为一个二进制文件,且这个二进制文件包含加载程序到内存并运行它所需的所有信息。
C运行时内存模型
进程在运行时为C程序提供了一个通用的运行时存储器映像。
Linux将这个运行时存储器映像组织成若干段的集合,它主要有两部分:进程虚拟存储器、内核虚拟存储器。进程虚拟存储器有我们熟悉的代码段、数据段、运行时堆、共享库段、用户栈。内核虚拟存储器包括内核中的代码和数据结构、与进程相关的数据结构。以32位系统的可执行文件的运行时存储器映像来说:
l 代码段总是从地址0x08048000处开始,它保存编译程序的机器代码
l data段在接下来的一个4KB对齐的地址处,保存已初始化的全局C变量和静态变量
l bss段记录的是未初始化的全局C变量,事实上它并不占据目标文件的任何空间,只是一个占位符
l 运行时堆在接下来的第一个4KB对齐的地址处,通过调用malloc库向上增长,用于程序的动态内存管理
l 共享库段,用于加载共享库、映射共享内存和文件I/O,使用mmap和unmap函数申请和释放新的内存区
l 用户栈占据进程地址空间的最高部分,并向下增长,用于存放调用过程中的局部变量、函数返回地址、参数
l 内核代码和数据、物理存储器,它们均被映射到所有进程共享的物理页面,这就为内核提供一个便利的方法来访问内存中任何特定的位置。对于每个进程来说他们均是一样的
l 最顶层的内核地址空间包括了与进程有关的数据结构,如页表、内核在进程的上下文结构task_struct和mm结构,内核栈
代码测试如下:
#include <stdio.h>
#include <stdlib.h>
int glob1=120;
int glob2;
extern int etext,edata,end;
int func2(){
intf2_local1,f2_local2;
printf("函数2的局部变量-\tf2_local1: %p \tf2_local2:%p\n",&f2_local1,&f2_local2);
}
int func1(){
intf1_local1,f1_local2;
printf("函数1的局部变量-\tf1_local1: %p \tf1_local2:%p\n",&f1_local1,&f1_local2);
func2();
}
int main(){
intm_local1,m_local2;
int*dyn_addr;
printf("代码段的结束地址:%p\n",&etext);
printf("初始化数据区的结束地址:%p\n",&edata);
printf("未初始化数据区的结束地址:%p\n",&end);
printf("运行时初始可用堆区域的边界地址:%p\n\n",(char *)sbrk(0));
printf("全局变量- glob1:%p \t glob2:%p\n",&glob1,&glob2);
dyn_addr=(int *)malloc(16);
printf("指针变量的值在堆区域-dyn_addr: %p\n",dyn_addr);
printf("函数地址-\t main:%p \tfunc1: %p \tfunc2:%p\n",main,func1,func2);
printf("main函数的局部变量-\tm_local1: %p \tm_local2:%p\n",&m_local1,&m_local2);
func1();
return 0;
}
输出如下:
结果看出这是一个运行在64位机器上的程序(64位的代码段总是从0x400000),函数地址main,func1,func2的地址均在代码区域中,全局变量glob1的地址在初始化数据区中,全glob2的地址在未初始化数据区中,指针遍历dyn_addr的值在堆区域中,局部变量m_local1、m_local2、f1_local1、f1_local2、f2_local1、f2_local2依次存放在栈(并且可知栈的增长方向是从高地址到低地址,堆的增长方向是从低地址到高地址)
参考书籍
《Memory management in C: The heap and the stack》
参考链接
http://www.cnblogs.com/xionghj/p/4319485.html
http://www.cnblogs.com/xionghj/p/4319463.html
http://www.cnblogs.com/xionghj/p/4319504.html