C++编译过程 静态链接 动态链接

C++程序的编译链接过程主要有预处理, 编译, 链接这几个阶段:
 1 预处理:
      预处理是在编译之前, 由编译器调用的一个独立程序, 即预编译处理器, 对源代码进行处理, 预处理主要负责以下工作:
      1) 替换宏
      2) 删除注释
      3) 处理预处理指令, 如#include, #ifndef

      源程序文件经过预处理后, 就形成一个包含所有必要信息的单个源文件, 一个源文件就是一个编译单元, 这个编译单元, 即源文件会被编译成同名的目标文件(.o或.obj)

  2 编译:
      编译过程负责以下工作:
      1) 语法分析, 检查语法错误
      2) 编译源单码, 即编译单元, 将编译单元中以文本形式存在的源代码编译成机器语言形式的目标文件
      内联函数的替换也发生在这一阶段, 编译过程中, 每个编译单元是相互独立的, 即每个源文件之间不知道对方的存在, 除了像include "xxx.cpp"这样极其错误的写法.
      在目标文件中, 除了有数据和二进制代码, 还有至少3张表: 未解决符号表, 导出符号表, 地址重定向表.   
      未解决符号表记录所有在该编译单元中引用(比如使用其它源文件中的函数, 全局变量等)但是定义不在该编译单元中的符号及其在该编译单元中出现的地址, 在链接阶段, 链接器会从其它目标文件的导出符号表中查找该表中记录的符号, 如果该表中记录的符号在链接阶段未全部找到, 就会报"unresolved external link"这样的链接错误.
      导出符号表记录该编译单元中定义的, 并且能够提供给其它编译单元使用的符号及其地址, 其它编译单元的未解决符号表中的记录的符号就需要从导出符号表中查找地址重定向表记录了该目标文件中所有对自身地址引用的记录, 这些记录实际上相当于在该目标文件中的地址偏移.

   3 链接:
      链接过程是将所有目标文件链接到一块, 形成一个可执行文件.
      链接器进行链接的时候, 首先决定各个目标文件在最终可执行文件里的位置, 然后访问所有目标文件的地址重定向表, 对其中记录的地址进行重定向(即加上该编译单元实际在可执行文件里的起始地址), 然后遍历所有目标文件的未解决符号表, 并且在所有的导出符号表中查找匹配的符号, 并在未解决符号表中所记录的位置上填写实际地址(即该符号在拥有其定义的目标文件中的实际地址),最后把所有目标文件的内容写在各自的位置, 就生成了可执行文件.

   内部链接与外部链接:
      外部链接: 编译单元中, 如果一个名称在链接期能提供给其它编译单元使用, 可以和其它编译单元交互, 那么这个名称就有外部链接; 以下情况有外部链接:
      1) 类的非inline函数, 包括静态和非静态成员函数
      2) 类静态成员变量
      3) 命名空间或全局的非静态自由函数, 非静态友元函数及非静态变量
      内部链接: 编译单元中, 如果一个名称是局部的, 并且在链接时不会与其它编译单元中的同样名称相冲突, 那么这个名称就有内部链接; 以下情况有内部链接:
      1) 所有的声明
      2) 命名空间或全局的静态自由函数, 静态友元函数, 静态变量的定义
      3) enum定义
      4) inline函数定义(包括自由函数和非自由函数)
      5) 类定义
      6) const常量定义
      7) union的定义
      extern关键字告诉编译器, 这个符号在别的编译单元中定义, 也就是要把这个符号放到未解决符号表中, 也就是外部链接.
       static关键字位于全局函数或变量声明的前面, 表明该编译单元不导出这个符号, 因此无法在别的编译单元里使用, 也就是内部链接.
       自由函数和变量默认是外部链接, const默认是内部链接, 可以通过使用extern和static改变链接属性
     
   常见问题
       头文件里一般只可以有声明, 不能有定义, 因为头文件可以被多个编译单元包含, 如果头文件里有定义, 那么每个包含该头文件的编译单元就都会同一个符号进行定义,如果该符号为外部链接, 就会出现重复定义的链接错误, 所以如果头文件如果要定义, 要么确保该头文件不会被多个编译单元引用, 要么确保定义的符号都具有内部链接.
      const默认为内部链接是为了能够在头文件中定义常量, 例如const int n = 0; 由于常量是只读的, 所以即使每个编译单元都有一份定义也没有关系, 不过有2种情况需要考虑:
      1 如果涉及对这个const对象取地址并且依赖于这个地址的唯一性, 那么在不同的编译单元里, 取到的地址不同.
      2 如果这个对象具有mutable属性, 某个编译单元对其进行修改, 其它编译单元看不到这一改变.
      如果一个定义于头文件中的变量拥有内部链接, 那么如果有多个编译单元包含该头文件, 即有多个编译单元中都定义了该变量, 则其中一个编译单元对该变量进行修改, 其它编译单元中就看不到这一改变.
      非静态函数和类的非inline函数默认是外部链接, 因为如果是内部链接, 可能会有人倾向于把定义写在头文件里, 这样的话一旦函数修改, 所有包含该头文件的编译单元都要重新编译.
      不允许在类的定义中对类的静态成员就地初始化, 因为类声明通常在头文件, 这样就相当于在头文件中定义类的静态成员, 而静态成员具有外部链接, 如果该头文件被包含在多个编译单元中, 就会出现链接错误.
      内联函数要定义于头文件里, 因为编译单元之间相互独立, 相互不知道, 如果内联函数定义于源文件中, 编译其它使用该函数的编译单元时没有办法找到函数定义, 因此也无法对函数展开.
      如果定义于头文件里的内联函数被拒, 那么编译器会自动在每个包含了该头文件的编译单元里定义这个函数并且不导出符号.

转载于:https://my.oschina.net/u/2420118/blog/811126

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值