Linux下C程序进程地址空间布局
我们在学习C程序开发时经常会遇到一些概念:代码段、数据段、BSS段(Block Started by Symbol) 、堆(heap)和栈(stack)。先看一张教材上的示意图(来源,《UNIX环境高级编程》一书),显示了进程地址空间中典型的存储区域分配情况。
从图中可以看出:
- 从低地址到高地址分别为:代码段、(初始化)数据段、(未初始化)数据段(BSS)、堆、栈、命令行参数和环境变量
- 堆向高内存地址生长
- 栈向低内存地址生长
还经常看到下面这个图(来源,不详):
先看一段程序。
- #include <stdio.h>
- #include <stdlib.h>
- int global_init_a=1;
- int global_uninit_a;
- static int static_global_init_a=1;
- static int static_global_uninit_a;
- const int const_global_a=1;
- int global_init_b=1;
- int global_uninit_b;
- static int static_global_init_b=1;
- static int static_global_uninit_b;
- const int const_global_b=1;
- /*上面全部为全局变量,main函数中的为局部变量*/
- int main()
- {
- int local_init_a=1;
- int local_uninit_a;
- static int static_local_init_a=1;
- static int static_local_uninit_a;
- const int const_local_a=1;
- int local_init_b=1;
- int local_uninit_b;
- static int static_local_init_b=1;
- static int static_local_uninit_b;
- const int const_local_b=1;
- int * malloc_p_a;
- malloc_p_a=malloc(sizeof(int));
- printf("\n &global_init_a=%p \t
- global_init_a=%d\n",&global_init_a,global_init_a);
- printf(" &global_uninit_a=%p \t
- global_uninit_a=%d\n",&global_uninit_a,global_uninit_a);
- printf(" &static_global_init_a=%p \t
- static_global_init_a=%d\n",&static_global_init_a,static_global_init_a);
- printf("&static_global_uninit_a=%p \t
- static_global_uninit_a=%d\n",&static_global_uninit_a,static_global_uninit_a);
- printf(" &const_global_a=%p \t
- const_global_a=%d\n",&const_global_a,const_global_a);
- printf("\n &global_init_b=%p \t
- global_init_b=%d\n",&global_init_b,global_init_b);
- printf(" &global_uninit_b=%p \t
- global_uninit_b=%d\n",&global_uninit_b,global_uninit_b);
- printf(" &static_global_init_b=%p \t
- static_global_init_b=%d\n",&static_global_init_b,static_global_init_b);
- printf("&static_global_uninit_b=%p \t
- static_global_uninit_b=%d\n",&static_global_uninit_b,static_global_uninit_b);
- printf(" &const_global_b=%p \t
- const_global_b=%d\n",&const_global_b,const_global_b);
- printf("\n &local_init_a=%p \t
- local_init_a=%d\n",&local_init_a,local_init_a);
- printf(" &local_uninit_a=%p \t
- local_uninit_a=%d\n",&local_uninit_a,local_uninit_a);
- printf(" &static_local_init_a=%p \t
- static_local_init_a=%d\n",&static_local_init_a,static_local_init_a);
- printf(" &static_local_uninit_a=%p \t
- static_local_uninit_a=%d\n",&static_local_uninit_a,static_local_uninit_a);
- printf(" &const_local_a=%p \t
- const_local_a=%d\n",&const_local_a,const_local_a);
- printf("\n &local_init_b=%p \t
- local_init_b=%d\n",&local_init_b,local_init_b);
- printf(" &local_uninit_b=%p \t
- local_uninit_b=%d\n",&local_uninit_b,local_uninit_b);
- printf(" &static_local_init_b=%p \t
- static_local_init_b=%d\n",&static_local_init_b,static_local_init_b);
- printf(" &static_local_uninit_b=%p \t
- static_local_uninit_b=%d\n",&static_local_uninit_b,static_local_uninit_b);
- printf(" &const_local_b=%p \t
- const_local_b=%d\n",&const_local_b,const_local_b);
- printf(" malloc_p_a=%p \t
- *malloc_p_a=%d\n",malloc_p_a,*malloc_p_a);
- return 0;
- }
下面是输出结果。
先仔细分析一下上面的输出结果,看看能得出什么结论。貌似很难分析出来什么结果。好了我们继续往下看吧。
接下来,通过查看proc文件系统下的文件,看一下这个进程的真实内存分配情况。(我们需要在程序结束前加一个死循环,不让进程结束,以便我们进一步分析)。
在return 0前,增加 while(1); 语句
重新编译后,运行程序,程序将进入死循环。
使用ps命令查看一下进程的pid
#ps -aux | grep a.out
查看/proc/2699/maps文件,这个文件显示了进程在内存空间中各个区域的分配情况。
#cat /proc/2699/maps
上面红颜色标出的几个区间是我们感兴趣的区间:
- 08048000-08049000 r-xp 貌似是代码段
- 08049000-0804a000 r--p 暂时不清楚,看不出来
- 0804a000-0804b000 rw-p 貌似为数据段
- 08a7e000-08a9f000 rw-p 堆
- bff73000-bff88000 rw-p 栈
我们把这些数据与刚才的程序运行结果进行比较,看看有什么结论。
&global_init_a=0x804a018 全局初始化:数据段 global_init_a=1
&global_uninit_a=0x804a04c 全局未初始化:数据段 global_uninit_a=0
&static_global_init_a=0x804a01c 全局静态初始化:数据段 static_global_init_a=1
&static_global_uninit_a=0x804a038 全局静态未初始化:数据段 static_global_uninit_a=0
&const_global_a=0x80487c0 全局只读变量: 代码段 const_global_a=1
&global_init_b=0x804a020 全局初始化:数据段 global_init_b=1
&global_uninit_b=0x804a048 全局未初始化:数据段 global_uninit_b=0
&static_global_init_b=0x804a024 全局静态初始化:数据段 static_global_init_b=1
&static_global_uninit_b=0x804a03c 全局静态未初始化:数据段 static_global_uninit_b=0
&const_global_b=0x80487c4 全局只读变量: 代码段 const_global_b=1
&local_init_a=0xbff8600c 局部初始化:栈 local_init_a=1
&local_uninit_a=0xbff86008 局部未初始化:栈 local_uninit_a=134514459
&static_local_init_a=0x804a028 局部静态初始化:数据段 static_local_init_a=1
&static_local_uninit_a=0x804a040 局部静态未初始化:数据段 static_local_uninit_a=0
&const_local_a=0xbff86004 局部只读变量:栈 const_local_a=1
&local_init_b=0xbff86000 局部初始化:栈 local_init_b=1
&local_uninit_b=0xbff85ffc 局部未初始化:栈 local_uninit_b=-1074241512
&static_local_init_b=0x804a02c 局部静态初始化:数据段 static_local_init_b=1
&static_local_uninit_b=0x804a044 局部静态未初始化:数据段 static_local_uninit_b=0
&const_local_b=0xbff85ff8 局部只读变量:栈 const_local_b=1
p_chars=0x80487c8 字符串常量:代码段 p_chars=abcdef
malloc_p_a=0x8a7e008 malloc动态分配:堆 *malloc_p_a=0
通过以上分析我们暂时可以得到的结论如下,在进程的地址空间中:
- 数据段中存放:全局变量(初始化以及未初始化的)、静态变量(全局的和局部的、初始化的以及未初始化的)
- 代码段中存放:全局只读变量(const)、字符串常量
- 堆中存放:动态分配的区域
- 栈中存放:局部变量(初始化以及未初始化的,但不包含静态变量)、局部只读变量(const)
这里我们没有发现BSS段,但是我们将未初始化的数据按照地址进行排序看一下,可以发现一个规律。
&global_init_a=0x804a018 全局初始化:数据段 global_init_a=1
&static_global_init_a=0x804a01c 全局静态初始化:数据段 static_global_init_a=1
&global_init_b=0x804a020 全局初始化:数据段 global_init_b=1
&static_global_init_b=0x804a024 全局静态初始化:数据段 static_global_init_b=1
&static_local_init_a=0x804a028 局部静态初始化:数据段 static_local_init_a=1
&static_local_init_b=0x804a02c 局部静态初始化:数据段 static_local_init_b=1
&static_global_uninit_a=0x804a038 全局静态未初始化:数据段 static_global_uninit_a=0
&static_global_uninit_b=0x804a03c 全局静态未初始化:数据段 static_global_uninit_b=0
&static_local_uninit_a=0x804a040 局部静态未初始化:数据段 static_local_uninit_a=0
&static_local_uninit_b=0x804a044 局部静态未初始化:数据段 static_local_uninit_b=0
&global_uninit_b=0x804a048 全局未初始化:数据段 global_uninit_b=0
&global_uninit_a=0x804a04c 全局未初始化:数据段 global_uninit_a=0
这里可以发现,初始化的和未初始化的数据好像是分开存放的,因此我们可以猜测BSS段是存在的,只不过数据段是分为初始化和未初始化(即BSS段)的两部分,他们在加载到进程地址空间时是合并为数据段了,在进程地址空间中没有单独分为一个区域。
还有一个问题,静态数据与非静态数据是否是分开存放的呢?请读者自行分析一下。
接下来我们从程序的角度看一下,这写存储区域是如何分配的。首先我们先介绍一下ELF文件格式。
一个程序编译生成目标代码文件(ELF文件)的过程如下,此图引自《程序员的自我修养》一书的一个图:
可以通过readelf命令查看EFL文件的相关信息,例如 readelf -a a.out ,我们只关心各个段的分配情况,因此我们使用以下命令:
将这里的内存布局与之前看到的程序的运行结果进行分析:
&global_init_a=0x804a018 全局初始化:数据段 global_init_a=1
&global_uninit_a=0x804a04c 全局未初始化:BSS段 global_uninit_a=0
&static_global_init_a=0x804a01c 全局静态初始化:数据段 static_global_init_a=1
&static_global_uninit_a=0x804a038 全局静态未初始化:BSS段 static_global_uninit_a=0
&const_global_a=0x80487c0 全局只读变量: 只读数据段 const_global_a=1
&global_init_b=0x804a020 全局初始化:数据段 global_init_b=1
&global_uninit_b=0x804a048 全局未初始化:BSS段 global_uninit_b=0
&static_global_init_b=0x804a024 全局静态初始化:数据段 static_global_init_b=1
&static_global_uninit_b=0x804a03c 全局静态未初始化:BSS段 static_global_uninit_b=0
&const_global_b=0x80487c4 全局只读变量: 只读数据段 const_global_b=1
&static_local_init_a=0x804a028 局部静态初始化:数据段 static_local_init_a=1
&static_local_uninit_a=0x804a040 局部静态未初始化:BSS段 static_local_uninit_a=0
&static_local_init_b=0x804a02c 局部静态初始化:数据段 static_local_init_b=1
&static_local_uninit_b=0x804a044 局部静态未初始化:BSS段 static_local_uninit_b=0
p_chars=0x80487c8 字符串常量:只读数据段 p_chars=abcdef
ELF 文件一般包含以下几个段 :
- .text section:主要是编译后的源码指令,是只读字段。
- .data section :初始化后的非const的全局变量、局部static变量。
- .bss:未初始化后的非const全局变量、局部static变量。
- .rodata字段 是存放只读数据
分析到这以后,我们在和之前分析的结果对比一下,会发现确实存在BSS段,地址为0804a030 ,大小为0x20,之前我们的程序中未初始化的的确存放在这个地址区间中了,只不过执行exec系统调用时,将这部分的数据初始化为0后,放到了进程地址空间的数据段中了,在进程地址空间中就没有必要存在BSS段了,因此都称做数据段。同理,.rodata字段也是与text段放在一起了。
在ELF文件中,找不到局部非静态变量和动态分配的内容。
以上有很多地方的分析,我在网上基本找不到很明确的结论,很多教材上也没有描述,只是我通过程序分析得出的结论,如有不妥之处,请指出,欢迎交流。
Linux下C程序进程地址空间布局相关推荐
- Linux下C程序进程地址空间布局[转]
我们在学习C程序开发时经常会遇到一些概念:代码段.数据段.BSS段(Block Started by Symbol) .堆(heap)和栈(stack).先看一张教材上的示意图(来源,<UNIX ...
- qt调用linux 进程,Linux 下qt 程序打包发布(使用linuxdelpoyqt ,shell 脚本)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/u014746574/article/d ...
- linux cat 进程,Linux下CAT程序的C实现
Linux下CAT程序的C实现代码片段: #include #include #include #define BUFSIZ 1024 void error(char *fmt, ...){ va_l ...
- linux查看某进程的连接,linux下查看指定进程的所有连接信息(转)
定位某个进程的网络故障时经常需要用到的一个功能就是查找所有连接的信息.通常查找某个端口的连接信息使用 ss 或者 netstat 可以轻松拿到,如果是主动与别的机器建立的连接信息则可以通过 lsof ...
- Linux下查看某个进程打开的文件数-losf工具常用参数介绍
Linux下查看某个进程打开的文件数-losf工具常用参数介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 在linux操作系统中,一切皆文件.通过文件不仅仅可以访问常规数据,还 ...
- Linux下ps查找进程用kill终止命令
Linux下ps查找进程用kill终止命令<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:offic ...
- linux c++ 程序运行时间,总结UNIX/LINUX下C++程序计时的方法
前言 良好的计时器可帮助程序开发人员确定程序的性能瓶颈,或对不同算法进行性能比较.但要精确测量程序的运行时间并不容易,因为进程切换.中断.共享的多用户.网络流量.高速缓存访问及转移预测等因素都会对程序 ...
- linux 跟踪程序执行过程,用pvtrace和Graphviz实现对linux下C程序的函数调用跟踪
用pvtrace和Graphviz实现对linux下C程序的函数调用跟踪 用pvtrace和Graphviz实现对linux下C程序的函数调用跟踪 1:功能介绍,使用本方法可以实现linux下C应用程 ...
- kill掉多个进程linux中的sudo,linux下批量kill进程的方法
--kill某个用户下的所有进程(用户为test) --pkill # pkill -u test --killall # killall -u test --ps # ps -ef | grep t ...
最新文章
- 【c语言】分解质因数
- Java基础知识回顾-6
- SQL Select语句完整的执行顺序:
- cad打印字体颜色很淡_收藏|50个CAD技巧,常画电路图的你一定得知道
- 线程基本编程——线程函数大全
- 没想到单位的bt下载的速度可以到这么快,满意了
- 升级android 6.0系统
- es6 Class 的 Generator函数
- android 自定义进度条 水量,Android自定义带水滴的进度条样式(带渐变色效果)...
- SSM框架笔记08:初探Spring——采用配置类与注解方式
- 将房子卖了五百万,存在银行,靠利息能过日子吗?
- 决定薪资高低的到底什么?
- 辨异 —— 有两人生日在同一天、只有两人生日在同一天
- BZOJ1013球形空间产生器sphere 高斯消元
- 二分搜索 POJ 1064 Cable master
- python补考卷子_1819级计算机专业补考(python程序设计)_章节测验,期末考试,慕课答案查询公众号...
- S3C2410,ARM920T,试题总结
- 关于VirtualBox运行出现“0x00000000指令引用的0x00000000内存。该内存不能为written”的解决方法
- office 2017 (或2007)如何在方框里打钩 或 插入可打钩的方框
- 五十部电影(4)—和青春有关
热门文章
- 第六节:教你如何在html中绑定数据
- java基于文件的map实现_Mybatis中返回Map的实现
- python可移植性强吗_你真的了解Python吗?本文可以让你更深度了解Python
- 获得诺贝尔奖之后影响力会下降?绘制精英科学家的职业生涯路线图
- 鉴定5分钟,准确率95.64% | 微生物所付钰组实现了微生物单细胞水平的快速准确鉴定...
- 计算机类自主招生推荐信,自主招生推荐信范文:中国人民大学自主招生推荐信...
- 【原型设计】第三节:Axure RP9 母版的使用说明以及操作教程
- Python | threading02 - 互斥锁解决多个线程之间随机调度,造成“线程不安全”的问题。
- PHP笔记-用户登录例子
- Wireshark笔记-ping,arp相关的实验(2台主机是否能通)