每天学一点c++:c/c++程序的编译过程

时间:2021-11-23 17:04:11

  编译程序完成从源程序到目标程序的翻译工作,是一个复杂的整体的过程。总的来说,c/c++程序的编译过程分为4个部分:预处理=>编译、优化阶段=>汇编过程=>链接过程,分别对应着预处理器,编译器,链接器等。

一、预处理:

  1.宏定义指令处理,即#define语句

    例如:#define Address  Gz ,#undef

   预处理器将程序中的所有的Address用Gz代替,但如果如果Address是常量,则不被替换,#undef取消宏定义,后面出现的Address不再被替换

  2. #ifdef,#else,#ifndef,#elif,#endif等条件指令,

    程序员可以根据需要,决定对哪些宏定义进行处理,将那些已经处理过的代码过滤掉。

  3.头文件包含的代码,即#include"filename"或者#include<filename>

    头文件一般包含了用#define定义的宏,各种变量,函数和类等的声明,函数定义一般不放在头文件里,除非是内联函数。

     4.特殊符号,预处理器可以识别一些特殊符号

    例如 #line 标识将被解释为当前行号(十进制数)

 

 

              #include <iostream> 

              #line 100 

              using namespace std; 

       int main(int argc,char *argv[])

       {

        cout << "_LINE_:"<<_LINE_<<endl;

        return 0;

       }

       //输出结果为:_LINE_:7,本来输出应该为7的,使用#line后,using namespace std;就变成第100行了

  5.预处理模块

    预处理工作一般由#pragma指令完成,#Pragma命令将设定编译器的状态或者是指示编译器完成一些特定的动作。#pramga指令对每个编译器给出了一个方法,在保持与C和c++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对每个编译器都是不同的。

  打开C标准库函数,如stdio.h,我们总能找到下面这一句指示编译器初始化堆栈:

  #ifdef _MSC_VER

  /* 

  *Currently, all MS C compilers for Win32 platforms default to 8 byte  

  * alignment. 

   */ 

  #pragma pack(push,_CRT_PACKING) 

  #endif   /* _MSC_VER */

     或者程序指示编译器去链接系统动态链接库或用户自定义链接库

  #pragma comment(lib,_T("GDI32.lib"));

  这里解释一下:_MSC_VER_宏

  _MSC_VER_是微软的预处理控制,

  MS:Microsoft的简写。

  C:MSC就是Microsoft的C编译器。

  VER:Version的简写。

  _MSC_VER的意思就是:Microsoft的C编译器的版本。

  微软不同时期,编译器有不同的版本:

  MS VC++10.0 _MSC_VER=1600

  MS VC++9.0 _MSC_VER=1500

  MS VC++8.0 _MSC_VER=1400

   ......

  

 二、编译、优化程序

  预处理过的代码,预编程后的输出文件中,只有常量,如字符串,数字,函数的声明,变量的定义等和c/c++的关键字。

    在《编译原理》中我们知道,一个程序的编译主要有下面几个步骤:

    1)词法分析

    2)语法分析

    3)语义分析

    4)中间代码生成

    5)代码优化

    6)目标代码生成

 词法分析:词法分析是编译过程的第一个阶段。这个阶段是从左到右一个字符一个字符地读入源程序,对构成源程序的字符流扫描和分解,从而识别出一个个单词。 语法分析:是在词法分析的基础上将单词序列分解成各类语法短语,如“程序“、”语句“、”表达式“等。可表示成语法树。依据的是语言的语法规则。

 语义分析:语义分析是审查源程序有无语义错误,为代码生成阶段收集类型信息

 中间代码生成:在进行了上述的语法分析和语义分析阶段的工作之后,有点编译程序将源程序变成一种内部表示形式,这种内部表示形式叫做中间代码或中间语言。

 代码优化:是对前阶段产生的中间代码进行变换或进行改造,目的是使生成的目标代码更加高效,即省时间和省空间。

 目标代码生成:是把中间代码变换成特定的绝对指令代码或可重定义的指令代码或汇编指令代码。

 

  对函数压栈方式(函数调用约定)的编译处理:

    __cdecl 是C DECLaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,这些参数由调用者清除,称为手动清栈。被调用函数不需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误

  _stdcall 是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retnX,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。

三、汇编程序:

  汇编过程实际上将编译生成的汇编语言翻译成目标机器指令的过程。

  代码段:包含的主要是程序的指令。一般是可读的和可执行的,但一般不可读。

  数据段:主要存放程序中药用到的各种全局变量或静态的数据。一般是可读,可执行,可写的。

四、链接程序:

   由汇编程序生成的目标文件并不能立即就被执行,其中还有许多没有解决的问题。例如,某个源文件中函数引用了一个与源文件中定义的某个符号(如变量或者函数调用等);在程序中国可能调用了某个库文件的函数,等等。所有的这些问题,都需要经过链接程序的处理才能得以解决。

   链接程序的主要工作就是将有关的目标文件按照一定的方式是彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些文件成为一个能够被操作系统装入并执行的统一整体。

  根据开发人员制定的痛苦函数链接方式不同,链接处理分为两种:

  (1)静态链接

  在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者多个相关函数的代码。

(2)动态链接

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

   对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

在linux下,gcc的编译过程如下:

 预编译 :
将.c 文件转化成 .i文件 

使用的gcc命令是:gcc –E 

对应于预处理命令cpp 

编译: 
将.c/.h文件转换成.s文件

 使用的gcc命令是:gcc –S

 对应于编译命令   cc –S

 汇编 :
  将.s 文件转化成 .o文件 

  使用的gcc 命令是:gcc –c

   对应于汇编命令是  as 

链接 :
  将.o文件转化成可执行程序 

  使用的gcc 命令是: gcc 

     对应于链接命令是  ld 
       总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。

每天学一点c++:c/c++程序的编译过程