这里并没不是讨论大学课程中所学的《编译原理》,只是写一些我自己对C++编译器及链接器的工作原理的理解和看法吧,以我的水平,还达不到讲解编译原理(这个很复杂,大学时几乎没学明白)。
  要明白的几个概念:
  1、编译:编译器对源文件进行编译,是把源文件中的文本形式存在的源代码翻译成机器语言形式的目标文件的过程,在这个过程中,编译器会进行一系列的语法检查。如果编译通过,会把对应的CPP转换成OBJ文件。
  2、编译单元:根据C++标准,每一个CPP文件是一个编译单元。每个编译单元之间是相互独立并且互相不可知。
  3、目标文件:由编译所生成的文件,以机器码的形式包含了编译单元里所有的代码和数据,还有一些期他信息,如未解决符号表,导出符号表和地址重定向表等。目标文件是以二进制的形式存在的。
  根据C++标准,一个编译单元(Translation Unit)是指一个.cpp文件以及这所include的所有.h文件,.h文件里面的代码将会被扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件,后者拥有PE(Portable Executable,即Windows可执行文件)文件格式,并且本身包含的是二进制代码,但是不一定能执行,因为并不能保证其中一定有main函数。当编译器将一个工程里的所有.cpp文件以分离的方式编译完毕后,再由链接器进行链接成为一个.exe或.dll文件。
  下面让我们来分析一下编译器的工作过程:
  我们跳过语法分析,直接来到目标文件的生成,假设我们有一个A.cpp文件,如下定义:
  int n = 1;
  void FunA()
  {
  ++n;
  }
  它编译出来的目标文件A.obj会有一个区域(或者说是段),包含以上的数据和函数,其中有n、FunA,以文件偏移量形式给出可能是下面这种情况:
  偏移量    内容    长度
  0x0000    n       4
  0x0004    FunA    ??
  注意:这只是说明,与实际目标文件的布局可能不一样,??表示长度未知,目标文件的各个数据可能不是连续的,也不一定是从0x0000开始。
  FunA函数的内容可能如下:
  0x0004 inc DWORD PTR[0x0000]
  0x00?? ret
  这时++n已经被翻译成inc DWORD PTR[0x0000],也是说把本单元0x0000位置的一个DWORD(4字节)加1。
  有另外一个B.cpp文件,定义如下:
  extern int n;
  void FunB()
  {
  ++n;
  }
  它对应的B.obj的二进制应该是:
  偏移量    内容    长度
  0x0000    FunB    ??
  这里为什么没有n的空间呢,因为n被声明为extern,这个extern关键字是告诉编译器n已经在别的编译单元里定义了,在这个单元里不要定义了。由于编译单元之间是互不相关的,所以编译器不知道n究竟在哪里,所以在函数FunB没有办法生成n的地址,那么函数FunB中是这样的:
  0x0000 inc DWORD PTR[????]
  0x00?? ret