1.程序的编译
  一般而言,大多数编译系统都提供编译驱动程序(complier driver),根据用户需求调用语言预处理器,编译器,汇编器和链接器.例如有如下历程:
//main.c

void swap();

int buf[2]={1, 2};

int main()
{
  swap();
  return 0;
}

//swap.c

int *bufp0 = &buf[0]
int *bufp1;

void swap()
{
  int temp;
  bufp1 = &buf[1];
  temp = *bufp0;
  *bufp0 = *bufp1;
  *bufp1 = temp;
}
  驱动程序首先运行C预处理器(cpp),它将C的源程序main.c翻译成一个ASCII码的中间文件main.i.接下来,驱动程序运行C编译器(ccl),将main.i翻译成一个ASCII汇编语言文件main.s.然后,驱动程序运行汇编器(as),它将main.s翻译成一个可重定位的目标文件main.o.具体过程如下图所示:

2.链接
  链接就是将不同部分的代码和数据收集和组合成为一个单一文件的过程,这个文件可被加载或拷贝到存储器执行.
  链接可以执行与编译时(源代码被翻译成机器代码时),也可以执行与加载时(在程序被加载器加载到存储器并执行时),甚至执行与运行时,由应用程序来执行.在现代系统中,链接是由链接器自动执行的.
  链接器分为:静态链接器和动态链接器两种.
2.1.静态链接器
  静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出.

静态链接器主要完成两个任务:
  1>符号解析:目标文件定义和引用符号.符号解析的目的在于将每个符号引用和一个符号定义联系起来.
  2>重定位:编译器和汇编器生成从地址零开始的代码和数据节.链接器通过把每个符号定义和一个存储器位置联系起来,然后修改所有对这些符号的引用,使得他们执行这个存储位置,从而重定位这些节.

目标文件:
  目标文件有三种形式:
  1>可重定位的目标文件:
  包含二进制代码和数据,其形式可以再编译时与其他可定位目标文件合并起来,创建一个可执行目标文件.
  2>可执行目标文件:
  包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行.
  3>共享目标文件:
  一种特殊的可重定位目标文件,可以再加载或运行时,被动态地夹在到存储器并执行.
  编译器和汇编器生成可重定位目标文件(包括共享目标文件),链接器生成可执行目标文件.

可重定位目标文件:
  EF头L以一个16字节的序列开始,这个序列描述了字的大小和生成该文件的系统字节顺序.ELF头剩下的部分包含帮助链接器解析和解释目标文件的信息.其中包括ELF头的大小,目标文件的类型(比如,可重定位,可执行,共享目标文件),机器类型,节头部表的文件偏移,以及节头部表中的表目大小和数量.不同节的位置和大小是节头部表描述的,其中目标文件中的每个节都有一个固定大小的表目.ELF格式的可重定位目标文件结构如下图:

.text:已编译程序的机器代码
.rodata:只读数据
.data:已初始化的全局C变量
.bss:未初始化的全局C变量.在目标文件中这个节不占实际空间,仅是一个占位符.
.sysmtab:一个符号表,存放在程序中被定义和引用的函数和全局变量的信息.
.rel.text:当链接器把这个目标文件和其他文件结合时,.text节中的许多位置都需要修改.一般而言,任何调用外部函数或者引用全局变量的指令都要修改.另一个方面,调用本地函数的指令则不需要修改.
.rel.data:被模块定义或引用的任何全局变量的信息.
.debug:一个调试符号表
.line:原始C源程序中的行号和.text节中机器指令之间的映射.
.strtab:一个字符串表,其中内容包括.symtab和.debug节中的符号表,以及节头部中的节名字.

符号和符号表
  每个可重定位目标模块m都有一个符号表,它包含m所定义和引用的符号的信息.在链接器上下文中,有三种不同的符号:
  1>由m定义并能被其他模块引用的全局符号.全局链接器符号对应于非静态的C函数以及被定义为不带C的static属性的全局变量.
  2>由其他模块定义并被模块m引用的全局符号.这些符号成为外部符号,对应于定义在其他模块中的C函数和变量.
  3>只被模块m定义和引用的本地符号.有的本地符号链接器符号对应于带static属性的C函数和全局变量.这些符号在模块m中的任何地方都可见,但是不能被其他模块引用.目标文件中对应于模块m的节和相应的源文件的名字也能获得本地符号.

符号表式有汇编器构造的,使用编译器输出到汇编语言.s文件中的符号.sysmab节中包含ELF符号表.这张符号表包含一个关于表目的数组.表目的格式如下:

typedef struct{
  int name;  //string table offset
  int value;  //section offset, or VM address
  int size;  //object size in bytes
  char type:4,  //data, func, section, or src file
       binding:4;  //local or global
  char reserved;  //unused
  char section;  //section header index, ABS, UNDEF, or COMMON
}Elf_Symbol;

2.1.1符号解析
  链接器解析符号引用的方法是将每个引用和它输入的可重定位目标文件按的符号表中的一个确定的符号定义联系起来.
  对于那些和引用定义在相同模块的本地符号的引用,符号解析式非常简单明了的.编译器只允许每个模块中的每个本地符号只有一个定义.编译器还确保静态本地变量,它们会有本地链接器符号,拥有唯一的名字.
  对于全局符号的引用解析,当编译器遇到一个不是在当前模块中定义的符号(变量或函数名)时,它会假设该符号式在其他某个模块中定义的,生成一个链接器符号表表目,并把它交给链接器处理.如果链接器在它的任何输入模块中都找不到这个被引用的符号,它就输出一条错误信息并终止.
  在编译时,编译器输出的每个全局符号给汇编器,或者是强,或者是弱,而汇编器把这个信息隐含地编码在可重定位目标文件的符号表中.函数和以初始化的全局变量是强符号,未初始化的全局变量是弱符号.
  根据符号的强弱,有如下规则:
  1>不允许有多个强符号
  2>如果有一个强符号和多个弱符号,则选择强符号
  3>如果有多个弱符号,则任选一个弱符号

与静态库链接
  所有编译系统都提供一种机制,将所有相关的目标模块打包为一个单独的文件,称为静态库,它可以用做链接器的输入.当链接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块.
  在unix系统中,静态库以一种称为存档的特殊文件格式存放在磁盘中.存档文件是一组连接起来的可重定位目标文件的集合,有一个头部描述每个成员目标文件的大小和位置.

链接器如何使用静态库来解析引用
  在符号解析阶段,链接器从左到右按照它们在编译驱动程序命令行上出现的相同顺序来扫描可重定位目标文件和存档文件.在这次扫描中,链接器位置一个可重定位目标文件集合E,这个集合中的文件会被合并起来形成可执行文件,和一个未解析的符号集合U,以及一个在前面输入文件中已定义的符号结合D.初始时,E,U,D都是空的.
  1>对于命令行上的每个输入文件f,链接器会判断f是一个目标文件还是一个存档文件.如果是一个目标文件,那么链接器把f添加到E,修改U和D来反映f中的符号定义和引用,并继续下一个输入文件.
  2>如果f是一个存档文件,那么链接器就尝试匹配U中未解析的符号由存档文件成员定义的符号.如果某个存档文件成员m,定义了一个符号来解析U中的一个引用,那么就将m加到E中,并且链接器修改U和D来反映m中的符号定义和引用.对存档文件中的所有成员目标文件都反复进行这个过程,知道U和D都不再发生变化.在此时,任何不包含在E中的成员目标文件都会被丢弃,而链接器将继续到下一个输入文件.
  3>如果当链接器完成对输入命令行的扫描后,U是非空的,那么链接器就会输出一个错误并终止.否则,它会合并重定位E中的目标文件,从而构建输出的可执行文件.

这种方式,导致了在输入命令时要考虑到,静态库和目标文件的位置,库文件放在目标文件的后面,如果库文件之间有引用关系,则被引用的库放在后面.

2.1.2重定位
  当链接器完成了符号解析这一步时,它就把代码中的每个符号引用和确定的一个符号定义(也就是,它的一个输入目标模块中的一个符号表表目)联系起来.此时,链接器就知道它的输入目标模块中的代码节和数据解的确切大小.然后就开始重定位步骤.重定位由两步组成:
  1>重定位节和符号定义:
  在这一步中,链接器将所有相同类型的节合并为一个新的聚合节.然后,链接器将运行时存储器地址赋值给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号.当这一步完成时,程序中的每个指令和全局变量都一个唯一的运行时存储器地址.
  2>重定位节中的符号引用:
  在这一步中,链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址.为了执行这一步,链接器依赖于称为重定位表目的可重定位目标模块中的数据结构.

重定位表目:
  当汇编器生成一个目标模块时,它并不知道数据和代码最终将存放在存储器中的什么位置.它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置.所以,无论何时汇编器遇到对最终位置未知的目标引用,它就会生成一个重定位表目,告诉链接器在将目标文件合并为可执行文件时,如何修改这个引用.代码的重定位表目放在.rel.text中.已初始化数据的重定位表目放在rel.data中.
  ELF重定位表目的格式如下:
  typedef struct{
    int offset;  //offset of the reference to relocate
    int symbol:24,  //symbol the reference point to
        type:8;  //relocation type
  } Elf32_Rel;

ELF定义了11中不同的重定位类型,其中最基本的两种重定位类型是:R_386_PC32(重定位一个使用32PC相关的地址引用)和R_386_32(重定位一个使用32位绝对地址的引用).

2.2.动态链接器
  共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并在存储器中和一个程序链接起来.这个过程称为动态链接,是由动态链接器完成的.
  共享库的共享在两个方面有所不同.首先,在任何给定的文件系统中,对于一个库只有一个.so文件.所有引用该库德可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库德内容那样被拷贝和嵌入到引用它们的可执行的文件中.其次,在存储器中,一个共享库的.text节只有一个副本可以被不同的正在运行的进程共享.

 
 

C语言编译和链接过程的更多相关文章

  1. 解密C语言编译背后的过程

    我们大部分程序员可能都是从C语言学起的,写过几万行.几十万行.甚至上百万行的代码,但是大家是否都清楚C语言编译的完整过程呢,如果不清楚的话,我今天就带着大家一起来做个解密吧. C语言相对于汇编语言是一 ...

  2. GCC编译和链接过程

    GCC(GNU Compiler Collection,GNU编译器套件),是由 GNU 开发的编程语言编译器.它是以GPL许可证所发行的自由软件,也是 GNU计划的关键部分.GCC原本作为GNU操作 ...

  3. C++, Java和C#的编译、链接过程解析

    总是感觉java是解释性语言,转载下一篇感觉写的容易理解的文章 转自 http://www.cnblogs.com/rush/p/3155665.html 1.1.1 摘要 我们知道计算机不能直接理解 ...

  4. C语言编译和链接

    编译链接是使用高级语言编程所必须的操作,一个源程序只有经过编译.链接操作以后才可以变成计算机可以理解并执行的二进制可执行文件. 编译是指根据用户写的源程序代码,经过词法和语法分析,将高级语言编写的代码 ...

  5. Linux中程序的编译和链接过程

    1.从源码到可执行程序的步骤:预编译.编译.链接.strip 预编译:预编译器执行.譬如C中的宏定义就是由预编译器处理,注释等也是由预编译器处理的. 编译: 编译器来执行.把源码.c .S编程机器码. ...

  6. 菜鸟在C语言编译,链接时可能遇到的两个问题

    最近在看 CSAPP (Computer Systems A Programmers Perspective 2nd) 的第七章 链接.学到了点东西,跟大家分享.下文中的例子都是出自CSAPP第七章. ...

  7. C/C++编译和链接过程详解 (重定向表,导出符号表,未解决符号表)

    详解link  有 些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错 ...

  8. (转载) C/C++编译和链接过程详解 (重定向表,导出符号表,未解决符号表)

    转载http://blog.csdn.net/neo_ustc/article/details/9024839 有 些人写C/C++(以下假定为C++)程序,对unresolved external ...

  9. c 编译和链接过程

    详解link  有 些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错 ...

随机推荐

  1. springMVC简单示例

    1.新建web工程 2.引入springframework架包 3.配置文件 web.xml <?xml version="1.0" encoding="UTF-8 ...

  2. mac 安装redis

    一.下载 官网http://redis.io/ (搞不懂为啥被墙) 二.安装 将下载的tar.gz文件复制到 /usr/local 文件夹下 解压 sudo tar -zxvf redis3.1.6. ...

  3. hdu2243考研路茫茫——单词情结(ac+二分矩阵)

    链接 跟2778差不多,解决了那道题这道也不成问题如果做过基本的矩阵问题. 数比较大,需要用unsigned longlong 就不需要mod了 溢出就相当于取余 #include <iostr ...

  4. unittest可能面临的问题以及解决方法

    问题1:用例的执行顺序 当使用unittest.main()时,用例的执行是按照ascall值的顺序来执行的,所以如果使用main()方法来执行用例的话,那么就需要通过命名来限制执行顺序,比如想要先执 ...

  5. 初学者:JSP登陆界面

    学生登陆查询系统 1 程序的主要功能及特点 实现一个登录界面的基本功能,具体要求: 登录界面login.jsp含有表单,用户能够输入用户名和密码,并提交表单给verify.jsp. Verify.js ...

  6. C:上台阶

    总时间限制: 1000ms 内存限制: 65536kB描述楼梯有n(100 > n > 0)阶台阶,上楼时可以一步上1阶,也可以一步上2阶,也可以一步上3阶,编程计算共有多少种不同的走法. ...

  7. guava学习--Ordering

    转载:http://www.cnblogs.com/peida/p/Guava_Ordering.html Ordering是Guava类库提供的一个犀利强大的比较器工具,Guava的Ordering ...

  8. 如何获取有性能问题的SQL

    1.通过用户反馈获取存在性能问题的SQL.   2.通过慢查日志获取存在性能的SQL.   启动慢查日志 slow_query_log=on   set global slow_query_log=o ...

  9. 反射+泛型+缓存 ASP.NET的数据层通用类

    using System; using System.Collections.Generic; using System.Text; using System.Reflection ; using S ...

  10. github的注册过程

    带着疑问打开了github.这是一个神奇的网站,因为它到处都是英语,对于我这种英语盲这简直太痛苦了.借助了百度翻译,我还是马马虎虎的完成了github的制作. 首先在它的登录界面下面有一个sign u ...