C程序运行的背后(1)
一个成功的男人背后,至少有一个伟大的女人;一个不成功的男人,至少有一双手。
而一个C程序,无论成功不成功,它的背后一定有一个操作系统,一个shell,一套工具链。
世界本就不公平。隐藏在显而易见的事实背后的,你若能看透,便可以站在对自己公平的那一端。
1、进程地址空间
一个进程一旦建立,就会自认为占有4G内存(X86_32),这个内存被称作虚拟内存,也就是进程的地址空间。在Linux下,进程地址空间的布局大致如下图所示,其中的用户空间大致由这些部分组成:
- 代码段
- 初始化数据段
- 未初始化数据段
- 堆
- 栈

这些段,反映到ELF格式的目标文件(object file)中,就又可能由许多不同的节(section)组成。节这个东西更加细致复杂,暂且不表。
代码段
保存的是可执行指令,通常是只读的,防止指令被程序自身修改。但程序是无法防止被人为修改,否则哪来那么多的修改器。Vim就可以直接编辑二进制文件,指令的机器码任意修改。
存储实例:
push %ebp
movl %esp, %ebp
初始化数据段
保存的是已初始化了的全局变量和静态变量,它可以进一步划分为只读区域和可读写区域。
存储实例:
Char *string = “hello world”(全局)
“hello world”在只读区域,指针string在可读写区域
而Char string[] = “hello world”(全局)
就只存储string在读写区域中。因为string已被分配存储空间。
Static int class = 6 (全局/局部)
全局的容易理解。局部静态变量的意义,在于函数调用完后,其占用的存储单元也不被释放。如此便不可以存放到栈中,而又已被初始化,那么存放到这个段自然是合理的。
未初始化数据段
通常称为bss段,名字来自于“block started by symbol”—由符号开始的块。存放于此段的变量,在程序执行之前就被初始化为0或Null指针。
注意,未赋值的指针会被初始化为空指针!如果程序中定义的指针没有初始化,而后面又引用它指向的内存区域,那么在Linux下会引发“段错误”。
栈
这就是个狗皮膏药,用处大,却难搞。函数调用时,对栈的操作基本上由编译器完成。函数一旦被调用,就会生成一个栈帧(stack frame),栈帧的范围由两个 “栈指针”寄存器%ebp、%esp限定。
存储实例:
Caller的返回地址;
Caller的寄存器信息,如%ebp,%eax;
Callee自身的局部变量
堆
用户手动分配内存的区域,malloc和free,谁用谁知道。另外,共享库和动态加载的模块,也存放于堆中。
那么问题来了,实际编译好的目标文件是否真的是这样的呢?
以一个非常简单的C程序—memlayout.c—作为例程:
int main() {
return ;
}
用GCC分别编译生成memlayout.o和memlayout文件,并查看它们的内存布局:
[root@localhost ~]# size memlayout.o
text data bss dec hex filename
memlayout.o
[root@localhost ~]# size memlayout
text data bss dec hex filename
memlayout
这个程序没有定义任何的变量,由memlayout.o可以看出,data、bss为0是符合预期的。
段依然还是那些段,可最终的可执行文件如何却把它们都搞大了?
我并没有调用exit,为何程序自动流产?
男人的直觉也很准的,特别是程序出轨的时候。凭男人的直觉,我想,一定是编译器(实质是链接器)在某个地方插了一脚。
这也是一个细琐的问题,先做简要说明,容以后再表。
2、程序的生命周期
编译好的C程序是躺在磁盘里的,这时只能叫文件。加载到内存并撒腿狂奔的时候,才叫进程。老师们也告诉过我们,一个运行的“hello world”也是一个进程。所以一定要先有一个进程环境,程序才有狂奔的空间。我的家里没有草原,所以董小姐没有理我。
一个C程序的前世今生大概是这样的:
- Shell首先创建一个子进程,设置好进程环境;
- 子进程调用execve而陷入内核;
- 内核调用加载器程序,加载器清理子进程环境后,再加载可执行文件到子进程环境中;
- 加载器跳转到该程序的入口点(entry point),开始执行C启动代码;
- 调用main函数,执行真正的C程序;
- 调用_exit,把控制交还给内核。

也就是说,在写好的main函数之前,编译器添加了一段C启动代码,是C程序执行之前的准备工作;在main函数之后,编译器至少添加(调用)了_exit()来保证进程的正确终止。这也是为什么,中间目标文件和最终可执行文件size相差悬殊,用户空间的程序总会终结的原因。
C程序运行的背后(1)的更多相关文章
- C程序运行的背后(2)
话说上回说到,C程序运行之前,必须要加载到其进程地址空间中.今儿咱就扯扯这个加载到底是怎么加载的. 一图胜前言,这个图简单说明了可执行文件加载过程的逻辑流,在此只做粗粒度概要说明.需要准确描述的,请出 ...
- elf 文件格式探秘——程序运行背后的故事
摘要:本文主要讲解elf文件格式,通过readelf命令结合底层的相关数据结构,讲解相关内容,分析程序运行的基本原理. 本文来源:elf 文件格式探秘——程序运行背后的故事 http://blog.c ...
- 查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究
原文:查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究 查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究 -理解性能疑点 1 引言 内容来自http://www.so ...
- 图文浅析APK程序运行的过程
概述 APK程序运行过程有别于FrameWork底层启动过程,它们是倆码事,本文将以图文方式总结一下APK启动的过程,主要分为一下部分 [1]基本概念 [2]APK过程 1 .新的知识点 [1]什么是 ...
- 从hello world 说程序运行机制
转自:http://www.cnblogs.com/yanlingyin/archive/2012/03/05/2379199.html 开篇 学习任何一门编程语言,都会从hello world 开始 ...
- golang获取程序运行路径
golang获取程序运行路径: /* 获取程序运行路径 */ func getCurrentDirectory() string { dir, err := filepath.Abs(filepath ...
- linux下实现在程序运行时的函数替换(热补丁)
声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的. ...
- 放在NSArray、NSDictionary等容器内的对象Item,Item中的property在程序运行过程中被无故释放
可能是被释放的property本身是OC对象而它的属性被误写成assign,例如: @interface MyItem : Object @property (nonatomic, assign) N ...
- ABAP程序运行锁定
转自http://www.cnblogs.com/aBaoRong/archive/2012/06/15/2550458.html ABAP 程序运行锁 1. create a Table ZRUNN ...
随机推荐
- oracle 归档模式、补充日志
1.归档模式: Oracle数据库有联机重做日志,这个日志是记录对数据库所做的修改,比如插入,删除,更新数据等,对这些操作都会记录在联机重做日志里.一般数据库至少要有2个联机重做日志组.当一个联机重做 ...
- Mysql存储之原生语句操作(pymysql)
Mysql存储之原生语句操作(pymysql) 关系型数据库是基于关系模型的数据库,而关系模型是通过二维表时实现的,于是构成了行列的表结构. 表可以看作是某个实体的集合,而实体之间存在联系,这个就需要 ...
- Keras自定义评估函数
1. 比较一般的自定义函数: 需要注意的是,不能像sklearn那样直接定义,因为这里的y_true和y_pred是张量,不是numpy数组.示例如下: from keras import backe ...
- [转载]FFmpeg完美入门[1] - FFmpeg介绍及安装
1 FFmpeg简介 FFmpeg是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL或GPL许可证(依据你选择的组件).它提供了录制.转换以及流化音视 频的完整解决方案.它包含了非常先 ...
- C#+TaskScheduler(定时任务)实现定时自动下载
C# /TaskScheduler /定时任务 /定时自动下载 3410 实现原理,客户是广电,在广电服务器创建一个FTP目录,然后每天自动从卫星上自动更新节目列表, 然后功能就是要每天定点一个时间自 ...
- hdu 5895(矩阵快速幂+欧拉函数)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5895 f(n)=f(n-2)+2*f(n-1) f(n)*f(n-1)=f(n-2)*f(n-1)+2 ...
- Redis学习-redis概述
最近刚刚接触了redis技术,对此有一些了解,这是简单做一点总结. Redis简介 首先,简单了解一下NoSQL(Not only sql),不要错误的理解为:没有SQL,而是不仅仅是SQL.NoSQ ...
- 【转】servlet/filter/listener/interceptor区别与联系
原文:https://www.cnblogs.com/doit8791/p/4209442.html 一.概念: 1.servlet:servlet是一种运行服务器端的java应用程序,具有独立于平台 ...
- vue引入自己写的js文件
话不多说,直接上代码呀~ 先来个结构图: 中规中矩的vue-cli就写了一个自己的js文件 那么我想要引入到vue组件里. 1.首先写我的js文件 2.引入到vue组件!!!一定要用{}把方法名拿过来 ...
- [转]C/C++关于全局变量和局部变量初始化与不初始化的区别
原文链接:http://www.kingofcoders.com/viewNews.php?type=newsCpp&id=189&number=4836955386 在C语言里,全局 ...