欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++编译过程

程序员文章站 2024-03-18 10:38:28
...

前言
由#pragma once和条件编译引发的思考。
最近在写程序的时候遇到一个问题,.h文件开头总有 #pragma once. 我想知道这到底是什么意思。通过查资料发现,#pragma once 和 条件编译的作用相同——防止代码被二次编译。其实就是为了,防止程序员无意之中多次 include 相同的文件,导致编译器多次编译相同代码,影响编译器效率,从而影响运行效率。这开始让我更加深入的思考,并又产生了一个新的疑问:两种方式有什么区别?到底与什么不同呢?原来条件编译是编译器规定的特殊符号,这些符号统称为预编译符,平时我们最常用的就是include<>。而#pragma once 其实是来自微软。查资料的过程中,我发现“预处理”和“二次编译”反复出现。直觉告诉我,如果能把编译过程搞清楚一些这两个符号的作用将会非常清楚。
于是我就先找了操作系统:
得到的内容如下:
书中把程序的运行过程,总的说成两部分:程序的装入和链接。
大致有几个阶段:
编写程序:撸代码。
编译:由编译程序将用户源代码编译成若干个目标模块。
链接:由链接程序将编译后形成的一组目标模块,以及所需库函数链接在一起,形成一个完整的装入模块。
装入:由装入程序将装入模块内存运行。

编译过程简介
C++程序编译过程图
名词
编译过程

编译过程简介

提到编译过程你会想到什么?自己写的程序怎么变成机器代码让计算机执行的?
VC6.0告诉我们是这样的:源代码->编译->链接->运行。
操作系统告诉我们是这样的:源代码->编译->链接->装入。
可是只能到达这个层面了吗?一定还有认了解的更加深入!

C++程序编译过程图

C++编译过程
上图是c++程序编译过程图,很直观的看到大致流程。从C语言到汇编语言,再到机器语言。在对流程描述之前,需要对一些涉及到的名词进行解释。

名词

编译:利用编译器,将C++语言编写的源程序处理成机器语言程序的过程。如果编译通过就会通过CPP产生OBJ文件 。

编译的五个阶段:词法分析、语法分析、语义检查和中间代码生成、代码优化,目标代码生成。

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

目标文件:编译后生成的文件,以机器代码的形式包含了编译单元的所有函数和数据、导出符号表、为解决符号表、地址重定向表等。
目标文件的类型:包括可重定位文件(.o、obj.)、共享的目标文件(库文件)、可执行文件,可重定位文件在编译之后产生,库文件、可执行文件要在链接之后产生。

库文件(共享的目标文件):此类文件有两种:静态库和动态库。静态库可以把其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件(多个文件揉成一个文件)。动态库可以分成连部分一个是对外开放的接口(有点像函数的声明)。另一部分是这些函数的具体实现,dll把这些函数的实现全部装到了一起。动态链接库在程序执行时才被调用。

汇编器和编译器:感觉汇编器是编译器的子集。好像不对

编译过程

编译过程可以分成2个部分:其中在C++中编译汇编常常放在一起,IDE中VC6.0是把两者分开的,剩下的MS IDE 将这些概念越来越弱化取而代之的是【】【】【】生成 build 运行 这些名词。

编译

编译的预处理阶段

什么叫预处理,其实就是粗加工,从机械角度来说就是先粗磨。为了更好的分析,更好的细磨。预处理阶段就对应着预处理命令。预编译是其同义词。

///pro-process commend 
#空指令,无任何效果
#include包含一个源代码文件
#define定义宏 deine a macro
#undef取消已定义的宏 undefine a macro
#if如果给定条件为真,则编译下面代码 
#ifdef如果宏已经定义,则编译下面代码
#ifndef如果宏没有定义,则编译下面代码
#elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif结束一个#if……#else条件编译块
#error停止编译并显示错误信息

处理

条件编译
头文件包含
特殊符号

__FILE__ 包含当前程序文件名的字符串
__LINE__  表示当前行号的整数
__DATE__ 包含当前日期的字符串
__STDC__  如果编译器遵循ANSI C标准,它就是个非零值
__TIME__ 包含当前时间的字符串
#error指令将使编译器显示一条错误信息,然后停止编译。
#line指令改变_LINE_与_FILE_的内容,它们是在编译程序中预先定义的标识符。
#pragma指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。

编译的预处理阶段,所做的事情基本上是对原程序的替代工作。经过此种替代,生成一个没有宏定义没有条件编译指令没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内筒有所不同。下一步,将把次输出的文件作为编译程序的输出而被翻译成为机器指令。

编译、优化阶段

编译的五个阶段,同时优化。一种优化不依赖具体计算机,另一种优化结合计算机硬件优化。生成汇编语言。

汇编

把汇编语言代码翻译成目标机器指令,生成目标文件(.o文件、.obj文件)。此过程会依赖机器的硬件和操作系统环境。.o、.obj文件至少要提供3张表。

  • 导出符号表
  • 未解决符号表
  • 地址重定向表

    导出符号表:即该目标文件可以提供的符号及地址
    未解决符号表:即找不到地址的符号的列表,告诉链接器这些符号没找到地址。
    地址重定向表:链接的时候,链接器会为目标文件的“未解决符号表”里的符号在其他目标文件中寻找地址,但是每个目标文件的地址都是从0x0000开始的,这样直接将对方文件中符号的地址拿过来用显然会是不正确的,为了区分不同的文件,链接器在链接时就会对每个目标文件的地址进行调整。在这个例子中,假如B.obj的0x0000被定位到可执行文件的0x00001000上,而A.obj的0x0000被定位到可执行文件的0x00002000上,那么实现上对链接器来说,A.obj的导出符号地地址都会加上0x00002000,B.obj所有的符号地址也会加上0x00001000。这样就可以保证地址不会重复。
    因为被加上了起始地址,所以符号在自身文件中的实际地址就不对了,需要再用一张地址重定向表记录符号相对自身文件的地址。
    至此编译阶段结束,还应该输出语法错误。

链接

链接有两种方式:动态链接和静态链接。
链接也有链接错误

相关标签: 编译