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

而一个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 ;

}

用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)的更多相关文章

  1. C程序运行的背后(2)

    话说上回说到,C程序运行之前,必须要加载到其进程地址空间中.今儿咱就扯扯这个加载到底是怎么加载的. 一图胜前言,这个图简单说明了可执行文件加载过程的逻辑流,在此只做粗粒度概要说明.需要准确描述的,请出 ...

  2. elf 文件格式探秘——程序运行背后的故事

    摘要:本文主要讲解elf文件格式,通过readelf命令结合底层的相关数据结构,讲解相关内容,分析程序运行的基本原理. 本文来源:elf 文件格式探秘——程序运行背后的故事 http://blog.c ...

  3. 查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究

    原文:查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究 查询在应用程序运行得很慢, 但在SSMS运行得很快的原因探究 -理解性能疑点 1      引言 内容来自http://www.so ...

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

    概述 APK程序运行过程有别于FrameWork底层启动过程,它们是倆码事,本文将以图文方式总结一下APK启动的过程,主要分为一下部分 [1]基本概念 [2]APK过程 1 .新的知识点 [1]什么是 ...

  5. 从hello world 说程序运行机制

    转自:http://www.cnblogs.com/yanlingyin/archive/2012/03/05/2379199.html 开篇 学习任何一门编程语言,都会从hello world 开始 ...

  6. golang获取程序运行路径

    golang获取程序运行路径: /* 获取程序运行路径 */ func getCurrentDirectory() string { dir, err := filepath.Abs(filepath ...

  7. linux下实现在程序运行时的函数替换(热补丁)

    声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的. ...

  8. 放在NSArray、NSDictionary等容器内的对象Item,Item中的property在程序运行过程中被无故释放

    可能是被释放的property本身是OC对象而它的属性被误写成assign,例如: @interface MyItem : Object @property (nonatomic, assign) N ...

  9. ABAP程序运行锁定

    转自http://www.cnblogs.com/aBaoRong/archive/2012/06/15/2550458.html ABAP 程序运行锁 1. create a Table ZRUNN ...

随机推荐

  1. javaScript语法和风格的检查工具

    一.JSLint. JSHint. JSCS. ESLint 1.JSLint是由Douglas Crockford开发的,可能是最早的JavaScript Lint工具.JSLint定义了一组编码约 ...

  2. P2622 关灯问题II (状态压缩入门)

    题目链接: https://www.luogu.org/problemnew/show/P2622 具体思路:暴力,尝试每个开关,然后看所有的情况中存不存在灯全部关闭的情况,在储存所有灯的情况的时候, ...

  3. 商城项目(ssm+dubbo+nginx+mysql统合项目)总结(4)

    我不会在这里贴代码和详细步骤什么的,我觉得就算我把它贴出来,你们照着步骤做还是会出很多问题,我推荐你们去看一下黑马的这个视频,我个人感觉很不错,一步一步走下来可以学到很多东西.另外,视频和相关文档的话 ...

  4. Linux CGI编程基础【整理】

    Linux CGI编程基础 1.为什么使用CGI? 如前面所见,任何的HTML均是静态网页,它无法实现一些复杂的功能,而CGI可以为我们实现.如:a.列出服务器上某个目录中的文件,对目录中的文件进行操 ...

  5. angular项目中使用angular-material2

    Step 1: Install Angular Material and Angular CDK npm install --save @angular/material @angular/cdk n ...

  6. Photon3Unity3D.dll 解析三——OperationRequest、OperationResponse

    OperationRequest 代表Operation操作的Request,包含Code和Parameters OperationCode  Byte类型的值,代表操作,由LiteOpCode定义了 ...

  7. vmware linux虚拟机连接ip设置

    首先: 点击VMware 编辑->虚拟网络编辑器: 然后选中VMnet8的查看NAT设置: 上图第二步(记下红框中网关地址和子网掩码): 第三步(用于设置虚拟机地址范围): 接下来就是设置虚拟机 ...

  8. LeetCode312. Burst Balloons

    Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by ...

  9. 一张图解AlphaGo原理及弱点

    声明:本文转载自(微信公众号:CKDD),作者郑宇 张钧波,仅作学习收录之用,不做商业目的. 近期AlphaGo在人机围棋比赛中连胜李世石3局,体现了人工智能在围棋领域的突破,作为人工智能领域的工作者 ...

  10. JavaWeb知识回顾-Servlet常用类、接口及其方法

    今天主要把servlet的一些常用的类.接口和方法简单回顾一下. javax.servlet包 1.javax.servlet.Servlet接口 接口用于开发servlet,所有的servlet都要 ...