C/C++编译过程

C/C++编译过程主要分为4个过程

1) 编译预处理

2) 编译、优化阶段

3) 汇编过程

4) 链接程序

一、编译预处理

(1)宏定义指令,如#define Name TokenString,#undef等。 对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,

但作为字符串常量的 Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。

(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。 这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。

预编译程序将根据有关的文件,将那些不必要的代码过滤掉

(3) 头文件包含指令,如#include "FileName"或者#include <FileName>等。 在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),

同时包含有各种外部符号的声明。 包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。

在程序中#include它们要使用尖括号(< >)。

另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。

(4)特殊符号,预编译程序可以识别一些特殊的符号。 例如在源程序中出现的#line标识将被解释为当前行号(十进制数),

上面程序实现了对宏line的运用

(5)预处理模块 预处理工作由#pragma命令完成,#Pragma命令将设定编译器的状态或者是指示编译器完成一些特定的动作。

#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。

依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。

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

#include "iostream"

#line 100

usingnamespace std;

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

{

cout<<"__LINE__:"<<__LINE__<<endl;

return 0;

}

/*--------------------

* 输出结果为:

* __LINE__:103

* 本来输出的结果应该是 7,但是用#line指定行号之后,使下一行的行号变为,

* 到输出语句恰为行103

---------------------*/

C/C++编译过程

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

二、编译、优化阶段

经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。

在《编译原理》中我们可以了解到一个编译器对程序代码的编译主要分为下面几个过程:

a) 词法分析

b) 语法分析

c) 语义分析

d) 中间代码生成

e) 代码优化

f) 代码生成

g) 符号表管理

h) 将多个步骤组合成趟

i) 编译器构造工具

在这里我们主要强调对函数压栈方式(函数调用约定)的编译处理

C与C++语言调用方式大体相同,下面是几种常用的调用方式:

__cdecl 是C DECLaration的缩写(declaration,声明),表示C语言默认的函数调用方法:所有参数从右到左依次入栈,

这些参数由调用者清除,称为手动清栈。被调用函数不需要求调用者传递多少参数,调用者传递过多或者过少的参数,

甚至完全不同的参数都不会产生编译阶段的错误。

_stdcall 是StandardCall的缩写,是C++的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,

最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retnX,X表示参数占用的字节数,

CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。函数在编译的时候就必须确定参数个数,

并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。

PASCAL 是Pascal语言的函数调用方式,在早期的c/c++语言中使用这种调用方式,

参数压栈顺序与前两者相反,但现在我们在程序中见到的都是它的演化版本,其实

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

#ifdef _MSC_VER

/*

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

* alignment.

*/

#pragma pack(push,_CRT_PACKING)

#endif /* _MSC_VER */

C/C++编译过程

质是另一种调用方式

_fastcall是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传递比较费时。因此_fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄存器不同。返回方式和_stdcall相当。

_thiscall 是为了解决类成员调用中this指针传递而规定的。_thiscall要求把this指针放在特定寄存器中,该寄存器由编译器决定。VC使用ecx,Borland的C++编译器使用eax。返回方式和_stdcall相当。

_fastcall 和 _thiscall涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。所以Windows上的COM对象接口都定义为_stdcall调用方式。

C中不加说明默认函数为_cdecl方式(C中也只能用这种方式),C++也一样,但是默认的调用方式可以在IDE环境中设置。简单的我们可以从printf函数看出

printf使用从从左至右压栈,返回int型并由_CRTIMP指定封在动态链接库中。

通过金典的hello world程序我们可以知道编译器对其argc和argv[]这两个参数进行了压栈,并且argc留在了栈顶

优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化处理主要分为下面几个过程:

1) 局部优化

a) 基本块的划分

b) 基本块的变换

c) 基本块的DAG表示

d) DAG的应用

e) 构造算法讨论

2) 控制流分析和循环优化

a) 程序流图与循环

/*金典的hello world*/

#include <stdio.h>

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

{

printf("hello world");

return 0;

}

_Check_return_opt_ _CRTIMP int __cdecl printf(_In_z_ _Printf_format_string_ constchar * _Format, ...);

#define CALLBACK _stdcall /* Windows程序回调函数*/

#define WINAPI _stdcall

#define WINAPIV _cdecl

#define PASCAL _stdcall /*在c++语言中使用了StandardCall调用方式*/

#define PASCAL _cdecl/*在c语言中使用了C DECLaration调用方式*/

C/C++编译过程

b) 循环

c) 循环的查找

d) 可归约流图

e) 循环优化

3) 数据流的分析与全局优化

a) 一些主要的概念

b) 数据流方程的一般形式

c) 到达一定值数据流方程

d) 可用表达式及其数据流方程

e) 活跃变量数据流方程

f) 复写传播

经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。

三、汇编过程

汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,

都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

目标文件由段组成。通常一个目标文件中至少有两个段: 代码段:该段中所包含的主要是程序的指令。

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

四、链接程序

由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。

例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);

在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,

使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

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

(1)静态链接 在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。

这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,

其中的每个文件含有库中的一个或者一组相关函数的代码。

(2) 动态链接

在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。

链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量

的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应

进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

C/C++编译过程

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动

态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一

些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一

定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害

C/C++编译过程的更多相关文章

  1. Android工程的编译过程

    现在很多人想对Android工程的编译和打包进行自动化,比如建立每日构建系统.自动生成发布文件等等.这些都需要我们对Android工程的编译和打包有一个深入的理解,至少要知道它的每一步都做了什么,需要 ...

  2. GCC编译过程

    以下是C程序一般的编译过程: gcc的编译流程分为四个步骤,分别为:· 预处理(Pre-Processing) 对C语言进行预处理,生成*.i文件.· 编译(Compiling) 将上一步生成的*.i ...

  3. Linux系统GCC常用命令和GCC编译过程描述

    前言: GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理 C语言.GCC 很快地扩展,变得可处理 C++.后来又 扩展能够支持更多编程语言,如Fortran. ...

  4. Hadoop源码编译过程

    一.           为什么要编译Hadoop源码 Hadoop是使用Java语言开发的,但是有一些需求和操作并不适合使用java,所以就引入了本地库(Native Libraries)的概念,通 ...

  5. gcc编译过程简述

    在linux系统上,从源文件到目标文件的转化是由编译器完成的.以hello.c程序的编译为例,如下: dfcao@linux: gcc -o hello hello.c 在这里,gcc编译器读取源文件 ...

  6. android 编译过程

    引用:http://www.cnblogs.com/devinzhang/archive/2011/12/20/2294686.html http://blog.sina.com.cn/s/blog_ ...

  7. [转]UE4 Blueprint编译过程

    Blueprint 编译概述   一.术语 Blueprint,像C++语言一下的,在游戏中使用前需要编译.当你在BP编辑器中,点击编译按钮时候,BP资源开始把属性和图例过程转换为一个类对象处理. 1 ...

  8. TextMate2 最新版下载及源码编译过程

    TextMate2 已经开源,我刚编译成功,如果有需要的同学可以点击下面百度网盘的链接下载.我系统版本是:Mac OS X 10.8.4. TextMate version 2.0-alpha.946 ...

  9. C语言的编译过程、安装gcc编译器以及设置环境变量

    以我对C语言编译过程的了解,我用了一点时间画了一个图,提供给大家参考一下,希望有些能对您的问题提上帮助. 前几天刚初步学习了C语言的编译过程,感触挺深的.在C语言中头文件其实起了一个很大的作用. 1. ...

  10. 关于一个程序的编译过程 zkjg面试

    http://blog.csdn.net/gengyichao/article/details/6544266 一 以下是C程序一般的编译过程: 从图中看到: 将编写的一个c程序(源代码 )转换成可以 ...

随机推荐

  1. path方法总结

    $.mobile.path.get(url);//获取URL地址的目录部分,就是除了a.html之外的那部分 jQuery.mobile.path.getDocumentBase(bool) //获取 ...

  2. jquery的defer

    deferred.promise() 和 .promise() 这两个API语法几乎一样,但是有着很大的差别.deferred.promise()是Deferred实例的一个方法,他返回一个Defer ...

  3. 人工打jar包

    (一)将可执行程序打成一个jar包 其中Yoyo为入口程序,因此将当前目录下workhard和Book.class.testEx.class.Yoyo.class打成一个jar包的命令如下: jar ...

  4. ibatis时间比较大小

     <![CDATA[          A.RFID_Time >= #StartTime#          ]]>时间搜索功能 A.RFID_Time <![CDATA[  ...

  5. UIImageView只显示一半

    本来正常的话,UIImageView会在UIScrollView内占满的,但是第一个UIImageView只占了高度的一半左右.如下图,红色的是UIScrollView的背景色,还有那么多没有填充,但 ...

  6. Unix系统编程()深入探究文件IO概述

    open调用将引入原子atomicity操作的概念. 将某一系统调用所要完成的各个动作作为不可中断的操作,一次性加以执行. 原子操作是许多系统调用得以正确执行的必要条件. 还介绍一个系统调用fcntl ...

  7. Java调用doNet webService方法

    doNet的webService 浏览器访问测试地址:http://192.168.4.17/JLWWS/sendCommand.asmx,出现 点击getDeviceValue方法,出现 上图的xm ...

  8. 配置sudo su

    买了UCloud的机器默认给的是root权限,从安全考虑,这个得改改,那就添加一个普通用户吧.. 可是那群民工又有话说了,得有root权限才能启动那些服务进程,每次都要输入root密码才能切换到roo ...

  9. error: Please reinstall the libcurl distribution - easy.h should be in &lt;curl-dir&gt;/include/curl/

    运行php-5.3.10 --enable-mbstring --enable-ftp --enable-gd-native-ttf --with-openssl --enable-pcntl --e ...

  10. NPOI例子

    例子链接:http://www.cnblogs.com/atao/tag/NPOI/default.html?page=1