精华内容
下载资源
问答
  • C/C++程序编译过程详解

    万次阅读 多人点赞 2017-11-13 14:51:06
    C/C++程序编译过程详解 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件...

    C/C++程序编译过程详解

    C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织,形成最终生成可执行代码的过程。过程图解如下:

    clip_image002

    从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。

    1. 编译过程

    编译过程又可以分成两个阶段:编译和汇编。

    编译

    编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段:

    编译预处理

    读取c源程序,对其中的伪指令(以# 开头的指令)和特殊符号进行处理。

    伪指令主要包括以下四个方面:

    1) 宏定义指令,如# define Name TokenString,# undef等。

    对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为字符串常量的 Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。

    2) 条件编译指令,如# ifdef,# ifndef,# else,# elif,# endif等。

    这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

    3) 头文件包含指令,如# include "FileName" 或者# include < FileName> 等。

    在头文件中一般用伪指令# define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。

    采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条# include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。

    包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/ usr/ include目录下。在程序中# include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在# include中要用双引号("")。

    4) 特殊符号,预编译程序可以识别一些特殊的符号。

    例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

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

    编译、优化阶段

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

    编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

    优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。

    对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。

    后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。

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

    汇编

    汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。

    目标文件由段组成。通常一个目标文件中至少有两个段:

    1) 代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

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

    UNIX环境下主要有三种类型的目标文件:

    1) 可重定位文件

    其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。

    2) 共享的目标文件

    这种文件存放了适合于在两种上下文里链接的代码和数据。

    第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;

    第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。

    3) 可执行文件

    它包含了一个可以被操作系统创建一个进程来执行之的文件。

    汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

    2. 链接过程

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

    例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

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

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

    1) 静态链接

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

    2) 动态链接

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

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

    3. GCC的编译链接

    我们在linux使用的gcc编译器便是把以上的几个过程进行捆绑,使用户只使用一次命令就把编译工作完成,这的确方便了编译工作,但对于初学者了解编译过程就很不利了,下图便是gcc代理的编译过程:

    clip_image004

    从上图可以看到:

    1) 预编译

    将.c 文件转化成 .i文件

    使用的gcc命令是:gcc –E

    对应于预处理命令cpp

    2) 编译

    将.c/.h文件转换成.s文件

    使用的gcc命令是:gcc –S

    对应于编译命令 cc –S

    3) 汇编

    将.s 文件转化成 .o文件

    使用的gcc 命令是:gcc –c

    对应于汇编命令是 as

    4) 链接

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

    使用的gcc 命令是: gcc

    对应于链接命令是 ld

    总结起来编译过程就上面的四个过程:预编译处理(.c) --> 编译、优化程序(.s、.asm)--> 汇编程序(.obj、.o、.a、.ko) --> 链接程序(.exe、.elf、.axf等)。

    4. 总结

    C语言编译的整个过程是非常复杂的,里面涉及到的编译器知识、硬件知识、工具链知识都是非常多的,深入了解整个编译过程对工程师理解应用程序的编写是有很大帮助的,希望大家可以多了解一些,在遇到问题时多思考、多实践。

    一般情况下,我们只需要知道分成编译和链接两个阶段,编译阶段将源程序(*.c) 转换成为目标代码(一般是obj文件,至于具体过程就是上面说的那些阶段),链接阶段是把源程序转换成的目标代码(obj文件)与你程序里面调用的库函数对应的代码连接起来形成对应的可执行文件(exe文件)就可以了,其他的都需要在实践中多多体会才能有更深的理解。

     


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

    ----------------------------------------------------作者  张彦升

    转自:https://www.cnblogs.com/mickole/articles/3659112.html

    展开全文
  • Qt程序编译过程

    千次阅读 2018-04-07 23:25:40
    qmake是一个协助简化跨平台进行专案开发的构建过程的工具程式,Qt附带的工具之一 。qmake能够自动生成Makefile、Microsoft Visual Studio 专案文件 和 xcode 专案文件。不管源代码是否是用Qt写的,都能使用qmake,...

    qmake是一个协助简化跨平台进行专案开发的构建过程的工具程式,Qt附带的工具之一 。qmake能够自动生成Makefile、Microsoft Visual Studio 专案文件 和 xcode 专案文件。不管源代码是否是用Qt写的,都能使用qmake,因此qmake能用于很多软件的构建过程。

    • 使用qmake生成与平台无关的pro文件。 qmake -project
    • 利用pro文件生成与平台相关的Makefile文件 。Makefile文件中包含了要创建的目标文件和可执行文件、创建目标文件所依赖的文件和创建每个目标文件时需要运行的命令等信息。 qmake
    • 使用make命令完成自动编译,make就是通过读入Makefile文件的内容来执行编译工作的。会为每个源文件生成一个对应的.o文件,最后将目标文件链接生成最终的可执行文件。 make debug // make release

    使用uic工具编译ui文件。e.g

    uic -o ui_hellodialog.h hellodialog.ui
    展开全文
  • linux程序编译过程

    千次阅读 2018-09-21 08:53:58
    高级语言需要通过翻译成机器语言才能执行,而翻译的方式分为两种,一种是编译型,另一种是解释型,因此我们基本上将高级语言分为两大类,一种是编译型语言,例如C,C++,Java,另一种是解释型语言,例如Python、Ruby...

    大家肯定都知道计算机程序设计语言通常分为机器语言、汇编语言和高级语言三类。高级语言需要通过翻译成机器语言才能执行,而翻译的方式分为两种,一种是编译型,另一种是解释型,因此我们基本上将高级语言分为两大类,一种是编译型语言,例如C,C++,Java,另一种是解释型语言,例如Python、Ruby、MATLAB 、JavaScript。


    本文将介绍如何将高层的C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程,包括四个步骤:

    • 预处理(Preprocessing)

    • 编译(Compilation)

    • 汇编(Assembly)

    • 链接(Linking)

    640?wx_fmt=png


    GCC 工具链介绍

    通常所说的GCC是GUN Compiler Collection的简称,是Linux系统上常用的编译工具。GCC工具链软件包括GCC、Binutils、C运行库等。


    GCC

    GCC(GNU C Compiler)是编译工具。本文所要介绍的将C/C++语言编写的程序转换成为处理器能够执行的二进制代码的过程即由编译器完成。


    Binutils

    一组二进制程序处理工具,包括:addr2line、ar、objcopy、objdump、as、ld、ldd、readelf、size等。这一组工具是开发和调试不可缺少的工具,分别简介如下:

    • addr2line:用来将程序地址转换成其所对应的程序源文件及所对应的代码行,也可以得到所对应的函数。该工具将帮助调试器在调试的过程中定位对应的源代码位置。

    • as:主要用于汇编,有关汇编的详细介绍请参见后文。

    • ld:主要用于链接,有关链接的详细介绍请参见后文。

    • ar:主要用于创建静态库。为了便于初学者理解,在此介绍动态库与静态库的概念:

      • 如果要将多个.o目标文件生成一个库文件,则存在两种类型的库,一种是静态库,另一种是动态库。

      • 在windows中静态库是以 .lib 为后缀的文件,共享库是以 .dll 为后缀的文件。在linux中静态库是以.a为后缀的文件,共享库是以.so为后缀的文件。

      • 静态库和动态库的不同点在于代码被载入的时刻不同。静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。在Linux系统中,可以用ldd命令查看一个可执行程序依赖的共享库。

      • 如果一个系统中存在多个需要同时运行的程序且这些程序之间存在共享库,那么采用动态库的形式将更节省内存。

    • ldd:可以用于查看一个可执行程序依赖的共享库。

    • objcopy:将一种对象文件翻译成另一种格式,譬如将.bin转换成.elf、或者将.elf转换成.bin等。

    • objdump:主要的作用是反汇编。有关反汇编的详细介绍,请参见后文。

    • readelf:显示有关ELF文件的信息,请参见后文了解更多信息。

    • size:列出可执行文件每个部分的尺寸和总尺寸,代码段、数据段、总大小等,请参见后文了解使用size的具体使用实例。


    C运行库

    C语言标准主要由两部分组成:一部分描述C的语法,另一部分描述C标准库。C标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类型声明和宏定义,譬如常见的printf函数便是一个C标准库函数,其原型定义在stdio头文件中。

    C语言标准仅仅定义了C标准库函数原型,并没有提供实现。因此,C语言编译器通常需要一个C运行时库(C Run Time Libray,CRT)的支持。C运行时库又常简称为C运行库。与C语言类似,C++也定义了自己的标准,同时提供相关支持库,称为C++运行时库。

    准备工作

    由于GCC工具链主要是在Linux环境中进行使用,因此本文也将以Linux系统作为工作环境。为了能够演示编译的整个过程,本节先准备一个C语言编写的简单Hello程序作为示例,其源代码如下所示:

    #include <stdio.h> 

    //此程序很简单,仅仅打印一个Hello World的字符串。
    int main(void)
    {
     printf("Hello World! \n");
     return 0;
    }


    编译过程

    1.预处理

    预处理的过程主要包括以下过程:

    • 将所有的#define删除,并且展开所有的宏定义,并且处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等。

    • 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。

    • 删除所有注释“//”和“/* */”。

    • 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。

    • 保留所有的#pragma编译器指令,后续编译过程需要使用它们。
      使用gcc进行预处理的命令如下:

    $ gcc -E hello.c -o hello.i // 将源文件hello.c文件预处理生成hello.i
                           // GCC的选项-E使GCC在进行完预处理后即停止

    hello.i文件可以作为普通文本文件打开进行查看,其代码片段如下所示:

    // hello.i代码片段

    extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
    # 942 "/usr/include/stdio.h" 3 4

    # 2 "hello.c" 2


    # 3 "hello.c"
    int
    main(void)
    {
     printf("Hello World!" "\n");
     return 0;
    }


    2.编译

    编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。

    使用gcc进行编译的命令如下:

    $ gcc -S hello.i -o hello.s // 将预处理生成的hello.i文件编译生成汇编程序hello.s
                           // GCC的选项-S使GCC在执行完编译后停止,生成汇编程序

    上述命令生成的汇编程序hello.s的代码片段如下所示,其全部为汇编代码。

    // hello.s代码片段

    main:
    .LFB0:
       .cfi_startproc
       pushq   %rbp
       .cfi_def_cfa_offset 16
       .cfi_offset 6, -16
       movq    %rsp, %rbp
       .cfi_def_cfa_register 6
       movl    $.LC0, %edi
       call    puts
       movl    $0, %eax
       popq    %rbp
       .cfi_def_cfa 7, 8
       ret
       .cfi_endproc


    3.汇编

    汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编相对于编译过程比较简单,通过调用Binutils中的汇编器as根据汇编指令和处理器指令的对照表一一翻译即可。

    当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o目标文件后,才能进入下一步的链接工作。注意:目标文件已经是最终程序的某一部分了,但是在链接之前还不能执行。

    使用gcc进行汇编的命令如下:

    $ gcc -c hello.s -o hello.o // 将编译生成的hello.s文件汇编生成目标文件hello.o
                           // GCC的选项-c使GCC在执行完汇编后停止,生成目标文件
    //或者直接调用as进行汇编
    $ as -c hello.s -o hello.o //使用Binutils中的as将hello.s文件汇编生成目标文件

    注意:hello.o目标文件为ELF(Executable and Linkable Format)格式的可重定向文件。

    4.链接

    链接也分为静态链接和动态链接,其要点如下:

    • 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。

    • 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。

      • 在Linux系统中,gcc编译链接时的动态库搜索路径的顺序通常为:首先从gcc命令的参数-L指定的路径寻找;再从环境变量LIBRARY_PATH指定的路径寻址;再从默认路径/lib、/usr/lib、/usr/local/lib寻找。

      • 在Linux系统中,执行二进制文件时的动态库搜索路径的顺序通常为:首先搜索编译目标代码时指定的动态库搜索路径;再从环境变量LD_LIBRARY_PATH指定的路径寻址;再从配置文件/etc/ld.so.conf中指定的动态库搜索路径;再从默认路径/lib、/usr/lib寻找。

      • 在Linux系统中,可以用ldd命令查看一个可执行程序依赖的共享库。

    由于链接动态库和静态库的路径可能有重合,所以如果在路径中有同名的静态库文件和动态库文件,比如libtest.a和libtest.so,gcc链接时默认优先选择动态库,会链接libtest.so,如果要让gcc选择链接libtest.a则可以指定gcc选项-static,该选项会强制使用静态库进行链接。以Hello World为例:

    • 如果使用命令“gcc hello.c -o hello”则会使用动态库进行链接,生成的ELF可执行文件的大小(使用Binutils的size命令查看)和链接的动态库(使用Binutils的ldd命令查看)如下所示:

      $ gcc hello.c -o hello
      $ size hello  //使用size查看大小
        text    data     bss     dec     hex filename
        1183     552       8    1743     6cf     hello
      $ ldd hello //可以看出该可执行文件链接了很多其他动态库,主要是Linux的glibc动态库
             linux-vdso.so.1 =>  (0x00007fffefd7c000)
             libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fadcdd82000)
             /lib64/ld-linux-x86-64.so.2 (0x00007fadce14c000)
    • 如果使用命令“gcc -static hello.c -o hello”则会使用静态库进行链接,生成的ELF可执行文件的大小(使用Binutils的size命令查看)和链接的动态库(使用Binutils的ldd命令查看)如下所示:

      $ gcc -static hello.c -o hello
      $ size hello //使用size查看大小
          text    data     bss     dec     hex filename
      823726    7284    6360  837370   cc6fa     hello //可以看出text的代码尺寸变得极大
      $ ldd hello
            not a dynamic executable //说明没有链接动态库

    链接器链接后生成的最终文件为ELF格式可执行文件,一个ELF可执行文件通常被链接为不同的段,常见的段譬如.text、.data、.rodata、.bss等段。

    分析ELF文件

    1.ELF文件的段

    ELF文件格式如下图所示,位于ELF Header和Section Header Table之间的都是段(Section)。一个典型的ELF文件包含下面几个段:

    • .text:已编译程序的指令代码段。

    • .rodata:ro代表read only,即只读数据(譬如常数const)。

    • .data:已初始化的C程序全局变量和静态局部变量。

    • .bss:未初始化的C程序全局变量和静态局部变量。

    • .debug:调试符号表,调试器用此段的信息帮助调试。

    640?wx_fmt=jpeg

    可以使用readelf -S查看其各个section的信息如下:

    $ readelf -S hello
    There are 31 section headers, starting at offset 0x19d8:

    Section Headers:
     [Nr] Name              Type             Address           Offset
          Size              EntSize          Flags  Link  Info  Align
     [ 0]                   NULL             0000000000000000  00000000
          0000000000000000  0000000000000000           0     0     0
    ……
     [11] .init             PROGBITS         00000000004003c8  000003c8
          000000000000001a  0000000000000000  AX       0     0     4
    ……
     [14] .text             PROGBITS         0000000000400430  00000430
          0000000000000182  0000000000000000  AX       0     0     16
     [15] .fini             PROGBITS         00000000004005b4  000005b4
    ……


    2.反汇编ELF

    由于ELF文件无法被当做普通文本文件打开,如果希望直接查看一个ELF文件包含的指令和数据,需要使用反汇编的方法。

    使用objdump -D对其进行反汇编如下:

    $ objdump -D hello
    ……
    0000000000400526 <main>:  // main标签的PC地址
    //PC地址:指令编码                  指令的汇编格式
     400526:    55                          push   %rbp
     400527:    48 89 e5                mov    %rsp,%rbp
     40052a:    bf c4 05 40 00          mov    $0x4005c4,%edi
     40052f:    e8 cc fe ff ff          callq  400400 <puts@plt>
     400534:    b8 00 00 00 00          mov    $0x0,%eax
     400539:    5d                      pop    %rbp
     40053a:    c3                          retq  
     40053b:    0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
    ……

    使用objdump -S将其反汇编并且将其C语言源代码混合显示出来:

    $ gcc -o hello -g hello.c //要加上-g选项
    $ objdump -S hello
    ……
    0000000000400526 <main>:
    #include <stdio.h>

    int
    main(void)
    {
     400526:    55                          push   %rbp
     400527:    48 89 e5                mov    %rsp,%rbp
     printf("Hello World!" "\n");
     40052a:    bf c4 05 40 00          mov    $0x4005c4,%edi
     40052f:    e8 cc fe ff ff          callq  400400 <puts@plt>
     return 0;
     400534:    b8 00 00 00 00          mov    $0x0,%eax
    }
     400539:    5d                          pop    %rbp
     40053a:    c3                          retq  
     40053b:    0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
    ……


    展开全文
  • Android应用程序编译过程简述

    千次阅读 2015-11-07 18:30:02
    引言Android编译过程是指Java代码以及xml等资源文件转变为.apk文件的过程,本文将简单阐述这一过程,但不会深入,建立这一概念,为了更好的学习Android。此文参考了《Android编程权威指南》一书。编译流程先看一下...

    引言

    Android编译过程是指Java代码以及xml等资源文件转变为.apk文件的过程,本文将简单阐述这一过程,但不会深入,建立这一概念,为了更好的学习Android。此文参考了《Android编程权威指南》一书。

    编译流程

    先看一下编译流程示意图
    编译流程示意图
    如图所示:

    • 首先,资源文件会被资源打包工具(aapt:Android Asset Packaging Tool)编译压缩;
    • 然后Java代码包括src以及自动生成的R.java文件会被Java编译器编译为Java字节码文件;
    • 接着通过交叉编译为适合在Android虚拟机上运行的.dex文件;
    • 最后根据.dex文件和已编译的资源文件创建APK文件。

    总结

    一般写应用时,不太关注应用程序的编译过程,但是要成为一个高手,这些作为基础支持,是必须储备的。
    ps:.apk文件要能够运行,必须要进行签名,开发阶段,在模拟器上运行,需要以debug key签名,后期发布给用户时,必须要用release key 签名。

    展开全文
  • 程序编译整个的过程

    千次阅读 2019-03-18 21:34:56
    程序编译过程 应用程序从用户编写的源文件到内存中执行的进程,大致分为了一下几个阶段:首先编译程序将源代码编译成了多个目标模块,其次通过链接程序将编译好的目标模块,以及所需要的一些库函数链接在一起,...
  • 程序编译的详细过程

    千次阅读 2020-12-07 09:44:15
    一、c/c++程序编译过程 C语言的编译连接过程把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行文件:win下是.obj,linux下是.so),通过编译和链接两个步骤实现。 为了我们编写的hello.c程序可以...
  • 程序编译的4个过程

    千次阅读 2019-01-11 15:00:11
    为了我们编写的hello.c程序可以被执行,驱动硬件电路工作,hello.c程序必须经过一些列处理步骤,将源程序转化为可执行性的目标程序。 而我们知道,机器语言就是处理器可以直接理解(与生俱来就能理解)的编程语言,...
  • C++ 程序编译过程

    千次阅读 2020-01-15 16:19:14
    文章目录说明1. 预处理2. 编译3. 汇编4. 链接 说明 C/CPP 程序从源文件到可执行文件需要经历 4 个阶段...编译: 由 .i 到 .a 的过程,注意此处的 .a 后缀不代表静态库。 汇编: 由 .a 到 .o 的过程。 链接: 由...
  • 程序到可执行程序编译过程

    千次阅读 2019-09-19 22:49:47
    一份源代码,从开始产生到成为可执行程序过程:预处理——编译——汇编——链接。 1、预处理 预处理又叫预编译,主要解释源文件中所有的预处理指令,包括头文件的展开和宏定义的替换,形成.i文件;具体细节...
  • 面试题总结之程序编译过程

    千次阅读 2017-08-19 23:59:47
    解答首先先上图对编译的整个过程有个感性的认识,然后再逐步分析各个过程。 以hello.c 程序为例# include main{ printf("hello\n"); }一个.c源程序需要经过预处理器生成.i文件,再经过编译器生成.s文件,再经过...
  • C/C++程序编译的四个过程

    千次阅读 2018-08-13 15:24:31
    我为什么会选择Linux下的g++编译器,就是因为g++可以看到程序编译到运行的过程做了些什么。而VS等集成开发环境看不到这些,并不是说VS工具不好,(VS还是相当好用的...)。对于学习来说,需要知道程序编译到运行...
  • 程序编译的四个过程

    万次阅读 2018-01-17 23:04:19
    编译程序生成可执行文件四个阶段: GCC驱动程序读取源文件.c比如hello.c,并把它翻译成一个可执行目标文件hello,需要经历一下四个阶段,如下图示:   预处理阶段。预处理器(cpp)根据以字符#开头的命令,...
  • Linuxc编译和ARM程序编译过程详解

    千次阅读 2016-02-24 18:55:30
    Linuxc程序编译过程 main.c 预处理 (把头文件展开,宏替换) gcc -E main.c -o main.i 编译(编译时候检查语法错误) gcc -S main.i -o main.s 汇编(把汇编代码编译成二进制) gcc -c main.s -o main.o 链接...
  • Java程序编译和运行的过程

    万次阅读 多人点赞 2018-09-05 16:00:17
    Java整个编译以及运行的过程相当繁琐,本文通过一个简单的程序来简单的说明整个流程。   如下图,Java程序从源文件创建到程序运行要经过两大步骤:1、源文件由编译器编译成字节码(ByteCode) 2、字节码由java...
  • C++程序编译过程详解

    千次阅读 2014-12-08 20:15:48
    一般来说,我们可以把C++程序编译过程分为以下四步 (1)编译预处理 主要进行源码级别上的操作,预处理器执行源码中的预处理命令(以‘#’号开头的语句),其中预处理命令可以分为以下几类 a. 宏定义命令[ #define ...
  • C语言的编译过程

    千次阅读 2019-03-01 11:21:42
    编译编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。  流程:C...
  • 一个C语言程序编译过程

    千次阅读 2018-08-10 11:01:48
    这是一个再简单不过的C语言程序: #include &amp;lt;stdio.h&amp;gt; int main() { printf(&quot;hello world!\n&quot;); return 0; } 执行gcc test.c,就使一个源文件变成了可执行文件,这...
  • c语言的编译过程详解

    千次阅读 2020-10-05 14:19:08
    c语言的编译过程详解 IDE的使用让很多和我一样的人对C/C++可执行程序的底层生成一知半解,不利于我们深入理解原理。在这里小结一下,望路过的大神指正~ 前言:从一个源文件(.c文件)到可执行程序到底经历了哪几步,...
  • 程序编译,链接过程

    万次阅读 多人点赞 2016-08-05 16:05:44
    C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作...
  • DB2数据库的sqc程序编译过程

    千次阅读 2017-01-16 11:39:42
     1 DB2的嵌入sql程序处理过程 嵌入SQL程序处理,由一个源程序创建为一个可执行文件(或库)的过程。如下图所示:  从上图看出,首先对源文件做预编译(precompiler),生成两部分文件:一部分是
  • C源程序完整编译过程

    千次阅读 2016-07-22 18:24:54
    下面总结一下编译的完整过程: C源程序-->预编译处理(.c)-->编译、优化程序(.s、.asm)-->汇编程序(.obj、.o、.a、.ko)-->链接程序(.exe、.elf、.axf等)。 一、预编译处理(cpp) 它主要包括四个过程 ...
  • 距离上一篇文章已经有一个月的时间了 我居然花了一个月的时间来研究计算机导论这个课题!!! ...但是身为有一名程序猿,你真的知道什么叫 “ 程序 ” 、“ 代码 ” 和 “ 编译 ” 吗? 你能用...
  • 编译原理是介绍如何将高级语言程序变换成低级语言程序的方法。 其理论基础坚实,其形式化系统不仅用于编译程序,还大量用于人工智能、多媒体技术、数据库等领域。 程序设计语言 低级程序语言 特定的计算机系统所...
  • 第一章 PX4-Pixhawk-程序编译过程解析

    千次阅读 2017-04-10 16:37:31
    第一章 PX4程序编译过程解析 PX4是一款软硬件开源的项目,目的在于学习和研究。其中也有比较好的编程习惯,大家不妨可以学习一下国外牛人的编程习惯。这个项目是苏黎世联邦理工大学的一个实验室搞出来的。该方案是...
  • C++程序编译过程简介

    千次阅读 2013-03-17 17:53:18
    编译过程主要分为4个过程  1) 编译预处理 预编译程序完成的工作,可以说成是对源程序的“替换”工作。经过这个过程,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。 宏定义命令;例如#...
  • Linux下C语言程序编译过程

    千次阅读 2016-04-15 12:00:18
    Linux下C语言程序编译过程 使用gcc编译程序时,编译工程分为4个阶段: (1)预处理:(Pre-Processing) (2)编译:(Compiling) (3)汇编:(Assembling) (4)链接:(linking) Linux程序员可以根据自己的需要让gcc在...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,019,357
精华内容 407,742
关键字:

程序编译过程