C程序运行的背后(1)

一个成功的男人背后,至少有一个伟大的女人;一个不成功的男人,至少有一双手。

而一个C程序,无论成功不成功,它的背后一定有一个操作系统,一个shell,一套工具链。

世界本就不公平。隐藏在显而易见的事实背后的,你若能看透,便可以站在对自己公平的那一端。

1、进程地址空间

一个进程一旦建立,就会自认为占有4G内存(X86_32),这个内存被称作虚拟内存,也就是进程的地址空间。在Linux下,进程地址空间的布局大致如下图所示,其中的用户空间大致由这些部分组成:

  1. 代码段
  2. 初始化数据段
  3. 未初始化数据段

这些段,反映到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 0;

}

用GCC分别编译生成memlayout.o和memlayout文件,并查看它们的内存布局:

[[email protected] ~]# size memlayout.o
   text       data        bss        dec        hex    filename
     66          0          0         66         42    memlayout.o
[[email protected] ~]# size memlayout
   text       data        bss        dec        hex    filename
   1055        272          4       1331        533    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相差悬殊,用户空间的程序总会终结的原因。

时间: 12-29

C程序运行的背后(1)的相关文章

C程序运行的背后(2)

话说上回说到,C程序运行之前,必须要加载到其进程地址空间中.今儿咱就扯扯这个加载到底是怎么加载的. 一图胜前言,这个图简单说明了可执行文件加载过程的逻辑流,在此只做粗粒度概要说明.需要准确描述的,请出门左转,看源码去吧. 1.  程序总是运行在进程上下文(context)中的,当输入./memlayout时,shell会创建一个子进程.除每个进程独有的专属信息外,子进程会继承父进程的大部分资源,如环境变量.进程空间映像等.也就是说,如果不重置子进程的内容,子进程会运行与父进程一样的程序.为了让子

图文浅析APK程序运行的过程

概述 APK程序运行过程有别于FrameWork底层启动过程,它们是倆码事,本文将以图文方式总结一下APK启动的过程,主要分为一下部分 [1]基本概念 [2]APK过程 1 .新的知识点 [1]什么是UI线程与Thread线程区别 UI线程并不陌生,但是这玩意到底是啥,与普通线程Thread有啥区别呢? 什么是UI线程: ActivityTread类所在的线程即为UI线程,负责用户交互,处理用户消息绘制界面等 区别: UI线程的ActivityTread中的Main方法已经使用Looper.pr

python入门学习--程序运行、注释

1.程序运行 1.1 python解释器运行 File-new File创建一个新的编辑窗口,输入下图中的内容 选择File-save保存程序(为纯文本文件).建立一个专门存放python项目的目录.然后为程序起一个有意义的名字,比如name.py.文件名以.py结尾非常重要. 现在可以用Edit-Run或者按下Crtl+F5键来运行程序了 输入名字(比如Frank),然后按回车键.会看到 1.2 命令提示符运行  2.注释 上图第一行称为注释.注释既为了让别人能够容易理解程序,也为自已回头再看

转 我修改的注册表,但是程序运行起来,还是记着以前的

我修改的注册表,但是程序运行起来,还是记着以前的,我查看了相关资料,说是修改只是暂时保存在memory,并没有保存到harddisk中,请高手指点一下,我用python写的,代码如下import win32apiimport win32con def RegSubkeySet(dbName, dbVersion):        key = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER,'Software\\DSA\\PRODIS Office\\

C#如何加载程序运行目录外的程序集 (转)

---恢复内容开始--- 尼玛,为了这个问题,纠结到差点吐出干血,赶紧记下来! 源地址:http://blog.csdn.net/dyllove98/article/details/9391325 我们的应用程序部署的时候,目录结构一般不会只有运行程序的目录这一个,我们可能在运行目录下建子目录,也可能使用System32目录,也可能使用其它第三方的程序集..Net程序集 首先会在GAC中搜索相应的版本,如果未找到则会应用程序配置文件中找(如果配置),最后到应用程序所在的路径搜索. 如何可以将程序

黑马程序员——java——获取一个应用程序运行的次数,如果超过5次,给出使用次数已到请注册的提示,并不要再运行程序

获取一个应用程序运行的次数,如果超过5次,给出使用次数已到请注册的提示,并不要再运行程序 import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Properties; public class Test5 { public static void main(String[] args) th

[视频讲解]Java(JDK的下载安装及第一个程序运行)

(JDK的下载安装及第一个程序运行) 内容:Java JDK 的安装以及HelloWorld 程序的运行 欢迎童鞋们前往围观 http://v.youku.com/v_show/id_XODA3MzkzNzMy.html 更多内容分享请关注 我的博客 http://www.xiaozhangwx.com 本视频由 小张网校 提供

【转】使程序在Linux下后台运行 (关掉终端继续让程序运行的方法)

一.为什么要使程序在后台执行 我们计算的程序都是周期很长的,通常要几个小时甚至一个星期.我们用的环境是用putty远程连接到日本Linux服务器.所以使程序在后台跑有以下三个好处: 1:我们这边是否关机不影响日本那边的程序运行.(不会像以前那样,我们这网络一断开,或一关机,程序就断掉或找不到数据,跑了几天的程序只能重头再来,很是烦恼) 2:不影响计算效率 2:让程序在后台跑后,不会占据终端,我们可以用终端做别的事情. 二.怎么样使程序在后台执行 方法有很多,这里主要列举两种.假如我们有程序pso

指定Qt程序运行的style,比如fusion(以前没见过QStyleFactory)

转载请注明文章:指定Qt程序运行的style,比如fusion 出处:多客博图 代码很简单,如下: #include <QtWidgets/QApplication>   #include <QStyleFactory>      int main(int argc, char *argv[])   {       QApplication::setStyle(QStyleFactory::create("Fusion"));       QApplicatio