VS中C/C++的编译流程

2022-07-16 08:26:19

前言

编译过程就是将源文件中的源代码翻译成机器语言,保存到目标文件中。如果编译通过,就会把CPP文件转换成OBJ文件

一、.CPP文件和.OBJ文件

1. .CPP文件

每个cpp就是一个编译单元,每个编译单元相互之间是独立且相互不知的。一个编译单元是指一个.CPP文件及这include的所有.h文件。.h文件里面的代码将会扩展到包含它的.cpp文件,然后编译器编译该.cpp文件为一个.obj文件,后者拥有PE(Portable Executable,Windows可执行文件)文件格式,并且本身包含的就是二进制代码,但是不一定能执行,因为并不能保证其中一定有main函数。当编译器将一个工程里的所有.cpp文件以分离的方式编译完毕后,再由链接器进行链接成为一个.exe或.dll文件。

2. .obj文件

<1>.obj就是目标文件,是你的程序经过编译程序编译后生成的,不能够直接执行,需要连接程序,连接后才能生成可执行文件,这样才能执行。这种文件一般是由机器代码组成的。但是有一些是由自己定义的一些伪指令代码,但是还需要专门的解释程序对其进行解释执行,连接程序是把目标代码和它所使用的库文件连接的程序。(obj文件只给出了程序的相对地址,而exe是绝对地址)。

<2>目标文件类型:

  1. 可重定位文件(.o .obj文件):其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。每个CPP会被编译成一个.o文件。
  2. 共享的目标文件(库文件):
    这种文件存放了适合于在两种上下文里链接的代码和数据。

第一种是链接程序(静态库)可把它于它与可重定位文件及共享的目标文件一起处理来创建另一个目标文件。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码
第二种是动态链接程序(动态库)将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映像。动态链接库在程序执行时才会被调用。
3. 可执行文件
一个可以被操作系统创建一个进程来执行的文件。
4. .o文件在编译后就能获得,但是库文件,可执行文件都需要在链接后才能获得。

二、编译过程

源代码->编译器->汇编代码->汇编器->目标代码->链接器->可执行程序(.dll文件不能够直接打开)

  1. 作用:
    编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,在转换为机器代码,生成目标文件(.obj)
  2. 编译:
    预处理阶段:
    <1>宏#define
    <2>条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。
    <3>头文件包含,#include
    <4>特殊符号

1.LINE标识将被解释为当前行号(十进制数)

2.FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换

  1. 编译优化阶段
    <1>针对代码优化,不依赖计算机本身
    <2>针对计算机进行优化
  2. 汇编
    把汇编语言代码翻译成目标机器指令,生成目标文件(.o .obj文件)。此过程会依赖机器的硬件和操作系统环境。
  3. .o文件需要至少提供三张表

<1>导出符号表: 即该目标文件可以提供的符号及地址

<2>未解决符号表:即找不到地址的符号的列表,告诉链接器这些符号没找到地址。

<3>地址重定向表:
链接的时候,链接器会为目标文件的“未解决符号表”里的符号在其他目标文件中寻找地址,但是每个目标文件的地址都是从0x0000开始的,这样直接将对方文件中符号的地址拿过来用显然会是不正确的,为了区分不同的文件,链接器在链接时就会对每个目标文件的地址进行调整。在这个例子中,假如B.obj的0x0000被定位到可执行文件的0x00001000上,而A.obj的0x0000被定位到可执行文件的0x00002000上,那么实现上对链接器来说,A.obj的导出符号地地址都会加上0x00002000,B.obj所有的符号地址也会加上0x00001000。这样就可以保证地址不会重复。

因为被加上了起始地址,所以符号在自身文件中的实际地址就不对了,需要再用一张地址重定向表记录符号相对自身文件的地址

三、链接过程

  1. 链接:链接程序的主要工作就是将有关的目标文件(库文件 .o文件)彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系装入执行的统一整体。
  2. 具体工作:
    当链接器进行链接的时候,首先决定各个目标文件在最终可执行的文件里的位置。然后访问所有目标文件的地址重定义表,对其中记录的地址进行重定向(加上一个偏移量,即该单元在可执行文件上的起始地址)。然后遍历所有的目标文件的未解决符号表,并且在所有的导出符号表里查找匹配的符号,并在未解决符号表中所记录的位置上填写实现地址。最后把所有的目标文件的内容写在各自的位置上,在做一些其他工作,就生成一个可执行文件。
  3. 链接方式:
    <1>静态链接:函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。

<2>动态链接:函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其他少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中的记录信息找到相应的函数代码。

四、C/C++中提供的一些特性

  1. extern:这就是告诉编译器,这个变量或函数在别的编译单元里定义了,也就是要把这个符号放到未解决符号表里面去(外部链接)。
  2. static:如果该关键字位于全局函数或者变量的声明前面,表明该编译单元不导出这个函数或变量,因为有些函数不能在别的编译单元中使用(内部链接)。如果是static局部变量,则该变量的储存方式和全局变量一样,但是仍然不导出符号。
  3. 默认链接属性:对于函数和变量,默认链接是外部链接,对于const变量,默认内部链接。
  4. 外部链接的利弊:外部链接的符号在整个程序范围内都是可以使用的,这就要求其他编译单元不能导出相同的符号(不然就会报duplicated external symbols)
  5. 为什么头文件里一般只可以有声名不能有定义:头文件可以被多个编译单元包含,如果头文件里面有定义的话,那么每个包含这头文件的编译单元都会对同一个符号进行定义,如果该符号为外部链接,则会导致duplicated external symbols链接错误。
  6. 为什么公共使用的内联函数要定义于头文件里:因为编译时编译单元之间互不知道,如果内联被定义于.cpp文件中,编译其他使用该函数的编译单元的时候没有办法找到函数的定义,因些无法对函数进行展开(内联函数不展开,即不采用在使用处标记函数代码再跳转的方式,而是直接将代码嵌入)。所以如果内联函数定义于.cpp里,那么就只有这个.cpp文件能使用它。
  7. h中的inline 函数可以被多个cpp包含而不造成符号冲突,因为它会被直接嵌入到调用的地方,内部联结不形成外部符号,对外不可见

简要说明编译流程

预处理——编译——汇编——链接。预处理器先处理各种宏定义,然后交给编译器;编译器编译成.s为后缀的汇编代码;汇编代码再通过汇编器形成.o为后缀的机器码(二进制);最后通过链接器将一个个目标文件(库文件)链接成一个完整的可执行程序(或者静态库、动态库)。

  • 作者:清鸿y
  • 原文链接:https://blog.csdn.net/qq_52269550/article/details/120613304
    更新时间:2022-07-16 08:26:19