精华内容
下载资源
问答
  • 编译链接过程

    千次阅读 2018-08-24 23:17:54
    编译链接过程 C/C++程序从文本到可执行文件之间是一个复杂的过程. 对于源代码(.c/.cpp)文件我们是不能直接运行的, 必须经过一系列的处理才能转化为机器语言, 再通过链接相应的文件转化为可执行程序. 这个过程称为...

    编译链接过程

    C/C++程序从文本到可执行文件之间是一个复杂的过程. 对于源代码(.c/.cpp)文件我们是不能直接运行的, 必须经过一系列的处理才能转化为机器语言, 再通过链接相应的文件转化为可执行程序. 这个过程称为编译链接过程. 本文篇幅较长, 想直接看分析过程点击这里
    下面是从源代码到可执行文件的整个编译链接的过程:

    整个编译链接过程无非就分为 编译过程链接过程


    <img src='/assert/compiler-linker/1.png'>

    1. 编译过程

    C文件编译过程又可分为: 编译汇编

    1.1 编译

    编译是指编译器读取源程序(字符流), 对之进行词法和语法的分析, 将高级语言指令转换为功能等效的汇编代码.

    简而言之就是将 高级语言(我们这里指C源文件) 代码 转化为 汇编代码

    高级语言指令转化为汇编代码有需要两个过程:

    1.1.1 预处理

    预处理过程通过预处理器来完成, 预处理器是程序中处理输入数据,产生能用来输入到其他程序的数据的程序。输出被称为输入数据预处理过的形式,常用在之后的程序比如编译器中.

    本文只讨论C预处理器, C预处理器是C语言、C++语言的预处理器。用于在编译器处理程序之前预扫描源代码,完成 头文件的包含, 宏扩展, 条件编译, 行控制(line control) 等操作。

    对于C/C++语言预处理一般分为以下几个过程:

    1.1.1.1 包含文件

    所谓包含文件即为头文件 #include 到的文件, 在预处理的过程中会将其加入到预处理器的输出文件中, 以供编译程序处理.

    #include <stdio.h>
    
    int main(void)
    {
        printf("Hello, world!\n");
        return 0;
    }
    1.1.1.2 条件编译
    if-else指令包括#if, #ifdef, #ifndef, #else, #elif and #endif .

    这些指令可以指定不同的宏来决定那些代码执行, 那些不执行. 在预处理的过程中就会将那些不执行的程序进行过滤, 预处理器只输出执行的代码到编译程序中.

    #if VERBOSE >= 2
      print("trace message");
    #endif
    
    #ifdef __unix__ /* __unix__ is usually defined by compilers targeting Unix systems */
    # include <unistd.h>
    #elif defined _WIN32 /* _WIN32 is usually defined by compilers targeting 32 or 64 bit Windows systems */
    # include <windows.h>
    #endif
    1.1.1.3 宏定义与扩展

    对于已经定义的宏在进行预处理的过程中直接用已经定义好的宏进行替换, 若定义了 #undef 以后出现的这种宏都不会替换.

    #define <identifier> <replacement token list>                    // object-like macro
    #define <identifier>(<parameter list>) <replacement token list>  // function-like macro, note parameters
    #undef <identifier>                                              // delete the macro
    1.1.1.4 特殊的宏与指令

    对于某些特殊的宏在进行预处理的时候直接进行替换, 比如 C/C++语言定义了标准宏 ____LINE, ____FILE 直接替换为当前行号和文件

    #define WHERESTR  "[file %s, line %d]: "
    #define WHEREARG  __FILE__, __LINE__
    #define DEBUGPRINT2(...)       fprintf(stderr, __VA_ARGS__)
    #define DEBUGPRINT(_fmt, ...)  DEBUGPRINT2(WHERESTR _fmt, WHEREARG, __VA_ARGS__)
    1.1.1.5 Token连接

    Token即为符号, 可以理解为变量名
    使用 ## 运算符(Token Pasting Operator) 可以将两个Token连接成一个Token.
    ## 运算符左侧或右侧如果是另一个宏名,这个宏名将不会被宏展开,而是按照字面值被当作一个token。因此,如果需要 ## 运算符左右的宏名做宏展开,需要使用两层宏的嵌套使用,其中外层的宏展开时也一并把##运算符左右的宏名做宏展开。

    #define DECLARE_STRUCT_TYPE(name) typedef struct name##_s name##_t
    
    DECLARE_STRUCT_TYPE(g_object); // Outputs: typedef struct g_object_s g_object_t;
    1.1.1.6 用户定义的编译错误与警告
    #error "error message"
    #warning "warning message"
    1.1.1.7 编译器相关的预处理特性
    #paragm //提供了编译器特定的预处理功能
    
    //openmp
    #pragma omp parallel for


    1.1.2 编译和优化

    这个过程即为通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码.
    这里进行汇编的过程是通过编译器进行执行的, 在编译器(Compiler)的作用下经过预处理器输入的程序就编译成了汇编代码.


    1.2 汇编

    汇编的过程实际上就是将汇编代码翻译成目标机器语言的过程. 生成的目标文件也就是与源程序在逻辑上等效的机器语言代码.
    生成的机器语言代码被称为目标代码, 生成的二进制文件被称为目标文件, 也成为二进制文件.
    典型的生成的目标文件的数据类型为:

    符号起始区块(BSS Block Started by Symbol的缩写)
    正文段(text segment 或译作代码段)//主要存放程序的指令, 可读可执行不可写
    数据段(data segment)//主要存放全局变量和静态数据. 可读可写可执行

    2. 链接过程

    链接过程是由链接器进行操作的. 链接器(英语:Linker),又译为链接器、连结器,是一个程序,将一个或多个由编译器或汇编器生成的目标文件外加库链接为一个可执行文件。
    在编译过程中已经得到了可执行文件, 在这里主要讨论外加库链接. 现在的大多数操作系统都提供静态链接动态链接这两种链接方式

    以下是连接过程:


    <img src='/assert/compiler-linker/300px-Linker.png'>

    2.1 链接

    2.1.1 静态链接(编译时)

    链接器将函数的代码从其所在地(目标文件或静态链接库中)拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
    优点: 只需保证在开发者的计算机有正确的库文件,在以二进制发布时不需考虑在用户的计算机上库文件是否存在及版本问题.
    缺点: 生成的可执行文件体积较大。当初正是为了避免此问题,才开发了动态库技术。

    2.1.1 动态链接 (加载, 运行时)

    所谓动态链接,就是把一些经常会共用的代码(静态链接的OBJ程序库)制作成DLL档,当可执行文件调用到DLL档内的函数时,操作系统才会把DLL档加载存储器内,DLL档本身的结构就是可执行档,当程序有需求时函数才进行链接。透过动态链接方式,存储器浪费的情形将可大幅降低。静态链接库则是直接链接到可执行文件。
    DLL档本身也是可执行文件, 在程序执行的时候直接进行动态调用即可.

    2.1.3 静态链接和动态链接的比较

    静态链接动态链接
    编译时加载, 运行时
    lib在编译时就组装进exe文件程序运行时exe文件可以动态的加载dll
    不用考虑计算机库文件版本节省内存, 维护性高
    整个软件包只有exe文件软件包中有exe和dll
    lib文件是外部函数和变量, 在编译时复制进目标程序, 后缀为.adll文件本身是可执行的, 在运行时动态链接, 可以包含源码, 数据, 资源的多种组合, 后缀为.so

    经过链接器的作用形成可执行文件, 最后还要进行一步操作, 进行打包. 即将生成的可执行文件(.exe, .dll, .lib)文件进行打包. 交付给计算机即可运行.


    3. 编译链接过程分析

    我们创建三个文件分别为test.h, test.cmain.c

    //test.c文件
    #include <stdio.h>
    #define __PI__ 3.141592654
    void print_hello(){
        printf("hello world!\n");
    }
    void print_string(char *s){
        printf("%s\n", s);
    }
    double getArea(int r){
        return r*r*__PI__;
    }
    //-------------------------------------------------
    //test.h文件
    //test.h
    void print_hello();
    void print_string(char *s);
    double getArea(int r);
    
    //-------------------------------------------------
    //main.c文件
    #include "test.h"
    #define __HELLO__ print_hello();
    #define __TEMP__ print_string("hello c!");
    
    int main(){
        //test.h --> hello
        print_hello();
        //__HELLO__
        __HELLO__
        //__TEMP__
        __TEMP__
    }
    

    3.1 预处理操作

    gcc -E main.c | tee main.i

    预处理代码 main.i

    # 1 "main.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 1 "main.c"
    # 1 "test.h" 1
    
    void print_hello();
    void print_string(char *s);
    double getArea(int r);
    # 2 "main.c" 2
    
    
    
    int main(){
    
        print_hello();
    
        print_hello();
    
        print_string("hello c!");
    }

    可以发现, 预处理操作将定义的宏全部展开, 包含头文件, 去掉注释等 具体的预处理操作详见1.1.1

    3.2 编译

    gcc -S main.c

    编译生成后缀为.s的汇编代码, 拿出main函数来分析以下

    _main:
    LFB13:
        .cfi_startproc
        pushl   %ebp
        .cfi_def_cfa_offset 8
        .cfi_offset 5, -8
        movl    %esp, %ebp
        .cfi_def_cfa_register 5
        andl    $-16, %esp
        subl    $16, %esp
        call    ___main
        call    _print_hello      //调用_print_hello函数
        call    _print_hello      //调用_print_hello函数
        movl    $LC3, (%esp)
        call    _print_string     //调用_print_string函数
        movl    $0, %eax
        leave
        .cfi_restore 5
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc

    3.2 汇编

    汇编生成机器码.o文件, 我们是看不懂的.
    main.otest.o

    //对于.c文件
    gcc -c main.c
    gcc -c test.c

    3.2 链接

    对于得到的上一步汇编得到的.o文件我们进行链接.

    gcc -o main main.o

    这里发现报错了.

    main.o:main.c:(.text+0xf): undefined reference to `print_hello'
    main.o:main.c:(.text+0x14): undefined reference to `print_hello'
    main.o:main.c:(.text+0x20): undefined reference to `print_string

    是因为test.o文件没有打包成我们需要的lib格式文件, 即.a后缀的文件. 进行打包. 打包后的test.a

    ar -rc test.a test.o

    再次链接

    gcc -o main main.o ./test.a

    链接成功, 执行main.exe

    hello world!
    hello world!
    hello c!

    代码在我的个人博客里, 请访问YancyKahn
    以上就是整个编译链接的过程了, 喜欢的话点个赞.


    参考文献

    [1.] http://blog.51cto.com/7905648/1297255
    [2.] http://www.ruanyifeng.com/blog/2014/11/compiler.html
    [3.] https://blog.csdn.net/shenjianxz/article/details/52130111
    [4.] https://zh.wikipedia.org/wiki/C预处理器
    [5.] https://zh.wikipedia.org/wiki/%E7%9B%AE%E6%A0%87%E4%BB%A3%E7%A0%81
    [6.] https://zh.wikipedia.org/wiki/%E9%93%BE%E6%8E%A5%E5%99%A8
    [7.] https://blog.csdn.net/shaderdx/article/details/49929147

    展开全文
  • C语言的编译链接过程详解

    千次阅读 2018-08-04 11:43:31
    我们将对C语言的这种处理过程称为编译链接编译就是把文本形式源代码翻译为机器语言形式的目标文件过程。 链接是把目标文件、操作系统的启动代码和用到的库文件进行组织最终形成可执行代码的过程。编译链接...

    学过C语言的人都应该知道,我们所编辑的C语言程序是不能直接放到机器上运行的,它只不过是一个带".c"后缀的文件(也称为源代码)而已,需要经过一定的处理才能转换成机器上可运行的可执行文件。我们将对C语言的这种处理过程称为编译与链接。

    编译就是把文本形式源代码翻译为机器语言形式的目标文件过程。

    链接是把目标文件、操作系统的启动代码和用到的库文件进行组织最终形成可执行代码的过程。编译和链接的过程图解如下:

    161005463.jpg

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

     

    其中编译过程又分为两个阶段:编译和汇编。

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

    第一个阶段是:预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置的文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,他把文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境所需要的可执行代码跟另一个环境所需要的可执行代码可能有所不同,因为可用的硬件体系结构和操作系统不同所致。在许多情况下(特别是在嵌入式开发中),可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应环境。

    预处理阶段主要是以下几方面的处理:

    1)、宏定义指令,如#define、M a;

    对于这种伪指令,预编译所要做的是将程序中的所有M用a来替换,一定要注意作为字符常量a则不被替换(因为已经是常量,其值已经是确定的)。与之相对应的还有#undef,则是将取消对某个宏的定义,使之在后面出现时再不被替换。

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

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

    3)、头文件包含指令,如#include等。

    在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含各种外部符号的声明。采用头文件的主要目的是为了使某些定义可以供多个不同的C源程序使用。因为当需要使用到这些定义的C源程序中,只需要加上一条#include语句即可,而不必在此文件中将这些定义重复一遍。预编译程序将把头文件中定义统统加入到它所产生的输出文件中,以供编译程序对之处理。在linux操作系统中包含到C源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include/目录下。在程序中使用它们,#include要使用尖括号<>;另外开发人员也可以定义自己的头文件,这些文件一般与C源程序放在同一目录下,此时在#include中要使用""。

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

    例如,在源程序中出现的LINE标识符将被解释为当前行号(十进制),FILE则被解释为当前被编译的C源程序的文件名称,FUNCTION则被解释为当前被编译的C源程序中的函数名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换,这些常常是配套使用,用来进行对程序的调试。

    最后需要着重强调一点的是预处理阶段并不属于预编译过程,这经常是初学者容易搞错的。

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

    第二个阶段是:编译、优化,经过预编译得到的输出文件中只有常量,一般都是一些指令。

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

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

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

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

     

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

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

    UNIX环境下主要有三种类型的目标文件:
    1)可重定位文件:其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。
    2)共享的目标文件:这种文件存放了适合于在两种上下文里链接的代码和数据。第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。
    3)可执行文件:它包含了一个可以被操作系统创建一个进程来执行的文件。汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

     

    链接过程是由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。 例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
    链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够按操作系统装入执行的统一整体。
    根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
    1)静态链接
    在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
    2) 动态链接
    在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
    对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小(没有将函数部分拷贝),并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害,例如移植性将大大降低。
    我们在linux使用的gcc编译器便是把以上的几个过程进行捆绑,使用户只使用一次命令就把编译工作完成,这的确方便了编译工作,但为了初学者了解编译过程,下图便给出了gcc代理的编译过程:

    224935190.jpg

    从上图可以看到:
    a、预编译
    将.c 文件转化成 .i文件
    使用的gcc命令是:gcc –E
    对应于预处理命令cpp

     

     

    b、编译
    将.c/.h文件转换成.s文件
    使用的gcc命令是:gcc –S
    对应于编译命令 cc –S

    c、汇编
    将.s 文件转化成 .o文件
    使用的gcc 命令是:gcc –c
    对应于汇编命令是 as

    d、链接
    将.o文件转化成可执行程序
    使用的gcc 命令是: gcc
    对应于链接命令是 ld

     

    总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。了解了这四个过程中所做的工作,对我们理解头文件、库等的工作过程是有帮助的,而且清楚的了解编译链接过程还对我们在编程时定位错误,以及编程时尽量调动编译器的检测错误会有很大的帮助的。

    展开全文
  • C++编译链接过程详解

    万次阅读 多人点赞 2018-09-01 19:27:48
    C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作...

    C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织,形成最终生成可执行代码的过程。过程图解如下:
    这里写图片描述
    从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。
    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代理的编译过程:
    这里写图片描述
    从上图可以看到:

    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++编译链接全过程

    千次阅读 2018-07-22 16:47:00
    平时我们所说的编译主要包括预编译编译、汇编三部分,这三部分分别都干什么工作,主要职能有哪些,接下来我们一步步探讨总结。 (一)预编译 (1)由源文件“.cpp/.c”生成“.i”文件,这是在预编译阶段完成的;...

    今天博文主要讨论的问题是:我们编写的程序代码是怎样运行起来的?到底运行的是什么内容?平时我们所说的编译主要包括预编译、编译、汇编三部分,这三部分分别都干什么工作,主要职能有哪些,接下来我们一步步探讨总结。

    (一)预编译

    (1)由源文件“.cpp/.c”生成“.i”文件,这是在预编译阶段完成的;gcc -E .cpp/.c --->.i

    (2)主要功能

    •  展开所有的宏定义,消除“#define”;
    •  处理所有的预编译指令,比如#if、#ifdef等;
    •  处理#include预编译指令,将包含文件插入到该预编译的位置;
    •  删除所有的注释“/**/”、"//"等;
    •  添加行号和文件名标识,以便于编译时编译器产生调试用的行号信息以及错误提醒;
    •  保留所有的#program编译指令,原因是编译器要使用它们;

    (3)缺点:不进行任何安全性及合法性检查

    (二)编译---核心

    编译过程就是把经过预编译生成的文件进行一系列语法分析、词法分析、语义分析优化后生成相应的汇编代码文件。

    (1)由“.i”文件生成“.s”文件,这是在编译阶段完成的;gcc -S .i --->.s

    (2)主要功能

    •  词法分析:将源代码文件的字符序列划分为一系列的记号,一般词法分析产生的记号有:标识符、关键字、数字、字符串、特殊符号(加号、等号);在识别记号的同时也将标识符放好符号表、将数字、字符放入到文字表等;有一个lex程序可以实现词法扫描,会按照之前定义好的词法规则将输入的字符串分割成记号,所以编译器不需要独立的词法扫描器;
    •  语法分析:语法分析器将对产生的记号进行语法分析,产生语法树----就是以表达式尾节点的树,一步步判断如何执行表达式操作。下图为一个语法树:

     

    如果存在括号不匹配或者表达式错误,编译器就会报告语法分析阶段的错误;相同的存在一个yacc程序可以根据用户输入的语法规则生成语法树;

    •  语义分析:由语法阶段完成分析的并没有赋予表达式或者其他实际的意义,比如乘法、加法、减法,必须经过语义阶段才能赋予其真正的意义;

    语义分析主要分为静态语义和动态语义两种;静态语义通常包括声明和类型的匹配、类型的转换。比如当一个浮点型的表达式赋值给一个整型的表达式时,其中隐含了一个浮点型到整型转换的过程。只要存在类型不匹配编译器会报错。经过语义分析后的语法树的所有表达式都有了类型。动态语义分析只有在运行阶段才能确定;

    • 优化后生成相应的汇编代码文件
    • 汇总所有符号

    (三)汇编:生成可重定位的二进制文件;(.obj文件)

    (1)由“.s”文件生成的“.obj”文件;gcc -c .s-->.o;

    (2)此文件中生成符号表,能够产生符号的有:所有数据都要产生符号、指令只产生一个符号(函数名);

    (四)链接

    链接阶段主要分为两部分:

    (1)合并所有“.obj”文件的段并调整段偏移和段长度(按照段的属性合并,属性可以是“可读可写”、“只读”、“可读可执行”,合并后将相同属性的组织在一个页面内,比较节省空间),合并符号表,进行符号解析完成后给符号分配地址;其中符号解析的意思是:所有.obj符号表中对符号引用的地方都要找到该符号定义的地方。在编译阶段,有数据的地方都是0地址,有函数的额地方都是下一行指令的偏移量-4(由于指针是4字节);可执行文件以页面对齐。

    符号重定位举例:main.c  extern int gdata;     test.c   int gdata = 10;  

                                 main.o  *UND* gdata    -------->test.o   gdata     //符号重定位

    在进行符号解析时要注意只对global符号进行处理,对于local符号不做处理;

    (2)符号的重定位(链接核心):将符号分配的虚拟地址写回原先未分配正确地址的地方

    对于数据符号会存准确地址,对于函数符号,相对于存下一行指令的偏移量(从PC寄存器取地址,并且PC中下一行指令的地址)

    (五)程序的运行

    (1)创建虚拟地址空间到物理空间的映射(创建内核地址映射结构体),创建页目录和页表;

    (2)加载代码段和数据段

    (3)把可执行文件的入口地址写到CPU的PC寄存器里

    (六)目标文件类型

    Linux下的ELF文件主要有以下四种:

    (1)可重定位文件.obj,这种文件包括数据和指令,可以被链接成为可执行文件(.exe)或者共享目标文件(.so),静态链接库可以归为这一类;

    (2)可执行文件.exe,这种文件包含了可以直接运行的程序,它的代表就是ELF可执行文件,他们一般都没有扩展名;

    (3)共享目标文件.so,这种文件包含了数据和指令,可以在以下两种情况下使用:一是链接器使用这种文件与其他可重定位文件和共享目标文件链接,二是动态链接器将几个共享目标文件与可执行文件结合,作为进程映像的一部分使用。

    (4)核心转储文件,当进程意外终止时,系统可以将该进程的地址空间的内容及种植的一些信息转储到核心文件中,比如core dump文件。

    (七)可重定位文件与可执行文件的结构比较

    在编译链接的全过程中,汇编完成后生成“可重定位的二进制文件.obj”,链接阶段完成后生成可执行文件.exe,这两者有何区别呢?可重定位文件为什么不可以运行?接下来将比较这种文件的结构布局,以回答上面的疑惑。

    当一个程序运行时,操作系统会给进程分配的虚拟地址空间以达到每个进程都有自己独立的运行空间,但是各个进程空间共享内核空间,在32位下,这个空间大小为4G,在64位下,这个虚拟地址空间为8G;下图为32为下虚拟地址空间的布局:

    其中内核空间中的ZONE_DMA 直接内存访问,占16M,用于磁盘与内存的文件数据交换;

    ZONE_NORMAL :平时使用的正常的内核空间;  

    ZONE_HIGHMEM:高端内存处理。处理高端内存大于1G的数据;(64位没有)由于此空间非常大以至于映射后的虚拟地址空间不足。

    (1)可重定位文件(.obj)的组织布局和可执行文件(.exe)组织格式的比较

    (1)readelf  -h *.o 查看 .o文件的文件头ELF HEADER信息,包括class(一般为32位)、data、program header、一些地址记录、size记录等;改变(函数入口地址0x0+符号0x0,汇编阶段完成后)

        readelf -S *.o  查看section headers 中的内容  包括段的内容、偏移量、属性等;

        objdump -d *.o       objdump -S *.o    得到汇编后的机器码文件 

       objdump -t *.o 查看符号表 objdump -h *.o 查看.o文件的各个段(常用的段.data/.text/.bss/.comment)

    (2)在虚拟地址空间上存在的.bss段主要存储未初始化的或者初始化为0的全局变量或者静态变量,但是在.obj和.exe中并不存在此段,那么上述中的数据存储在文件的哪里呢?答案是存储在了“*.comment*”块中,这是因为存在强弱数据类型所导致的,请看下图中所示的情况:

    原则上根据.bss的存储内容可以得知gdata3其空间存储,但是却放在了.comment块中,gdata3是一个弱类型,其原因是由于我们不确定其他文件是否会存在同名强类型或者大于其字节数的弱类型出现,造成外文件的变量引用,因此,先将其存放在*COM*中;强弱类型的区分为:强类型(已经初始化的变量)、弱类型(未初始化的变量)。使用规则是:

    在.c文件中,假如我们同一目录下的main.c和test.c文件中两者优先规则:

    (1)两个文件中都同时定义了int类型的x变量,那么在编译时会提醒有重定义类型;

    (2)两个文件中强类型和弱类型都存在时,选择强类型;

    (3)两个文件中弱类型同时出现时,选择字节数大的弱类型;

    从上图中看到.obj文件中的段信息中.bss  和.comment占据同一地址,说明.bss段并未占据文件空间,只占据虚拟地址空间;那么我们如何知道虚拟地址空间中.bss段是否存有数据。请看下图所示布局信息:

    从上面两幅图中可以看到用readelf -h *.o可以查看文件头ELF HEADER 信息,其中包括section header,而通过readelf  -S *.o可以查看到段的所有信息,从而看到.bss段是否存有数据。

    (3)从obj 和exe的组织形式比较中发现,exe文件比obj多了一个program header 域,可使用readelf -l 可执行文件名 查看program header域的具体信息,如下图所示:

    由于我们运行程序只加载数据和指令,并且我们所有的obj文件和exe文件都是以“页”为对齐方式,同时每个页存储的内容按照属性进行分页存储,所以program header有两个加载页面,只有将数据和指令存储于页中才能真正的给符号分配地址从而运行程序。数据有只读和只写、指令有只读和执行,故而可以根据这个属性确定那些段应该放在哪一个页中,这个属性以及只能加载数据和指令决定了只存在两个load页在磁盘上。到这里为止,我们已经准备好了这个程序可以运行的所有条件。此时可执行文件里的内容按照load的布局被存储在磁盘中,那么如何运行呢?由下面这幅图来说明:

    从上图就可以看出一个程序从编译链接到运行的全过程。

    欢迎大家留言指出不足。

    展开全文
  • 编译链接原理

    千次阅读 2018-07-04 21:31:03
    在这里将简单介绍下程序的编译链接原理。 在ANSI C的任何一种实现中,存在两种环境翻译环境和执行环境。翻译环境主要将源代码转化为可执行的机器指令。执行环境用于实际执行代码。在翻译环境中,主要进行编译和...
  • C++编辑编译链接运行

    千次阅读 2018-04-17 09:20:22
    从写一个简单的“hello world!”到完成一个大型程序,当...了解这五个过程中所做的工作,对我们理解头文件、库文件等在程序中的作用是有帮助的,而且如果能够清楚的了解编译链接过程,在编程时定位错误,纠正错误,...
  • C语言的编译链接执行过程

    千次阅读 2018-10-31 11:01:47
    C语言的编译链接执行过程 ​ 机器不能识别高级语言,而对我们来说高级语言更方便理解,这需要编译器的帮助才能完成中间的转化过程:将高级语言转化成机器能识别的二进制文件。 ​ 而一个C程序在执行过程中,需要经历...
  • Windows下gcc编译链接

    千次阅读 2018-05-09 22:54:00
    在Windows的DOS下实现gcc编译链接 这里主要看的是两篇写的很详细的文章 C语言多文件编译初探(一) C语言多文件编译初探(二) 1.首先,你的Windows电脑的编译器需要是gcc,不清楚的话按`win+r`,输入cmd,...
  • C++编译链接原理简介

    千次阅读 多人点赞 2016-08-11 23:43:05
    在实习的过程中,偶尔会在编译代码的时候出现莫名其妙的链接错误,或者更惨的是,编译链接通过了,运行的时候出现莫名其妙的coredump,查了半天原来是.a静态库更新了导致.h文件和.o文件不一致。受够了被这些错误支配...
  • 使用GCC手动C 的编译链接

    千次阅读 2017-10-26 10:31:16
    1.在工程目录创建一个 handC 的目录 、 创建 子目录  src 仅存储 C文件  inc 存储头文件  obj 存储编译后的对象文件  bin 存储连接后形成的库或执行文件 ...执行 编译操作 ...执行链接
  • linux下C程序的编译链接

    千次阅读 2017-12-09 17:26:57
    linux下C程序的编译可以分为四个步骤,分别是预处理,编译,汇编和链接。 1.预编译: 首先预处理完成预替换、去注释、头文件按照路径展开、以及条件编译。 建立一个test.c文件。里面写入注释、宏定义、头文件、...
  • gcc编译链接头文件和库文件

    千次阅读 2019-11-24 23:51:31
    GCC与头文件 gcc -参数: -I ( i 的大写) :指定头文件路径(相对...gcc头文件的搜索路径: 头文件 gcc在编译时如何去寻找所需要的头文件: 头文件的搜索会从-I指定的目录开始; 然后搜索gcc的环境变量 C_INCLU...
  • c++ 编译链接, make, cmake 整理

    千次阅读 2016-09-22 10:52:01
    - 2016-10-27 补充了cmake及cmake生成nmake,整理了windows下有关编译的目录,有助于理解vs的x86和x64编译过程。 - 2017-6-7 加入了一个gcc选项,关于库的依赖顺序前言无论在windows上还是linux上我
  • C++头文件、源文件的编译链接

    千次阅读 多人点赞 2017-12-11 15:17:02
    一、C++编译模式通常,在一个C++程序中,只包含两类文件——.cpp文件和.h文件。其中,.cpp文件被称作C++源文件,里面放的都是C++的源代码;而.h文件则被称作C++头文件,里面放的也是C++的源代码。C+ +语言支持“分别...
  • 编译链接编译链接过程、预编译、存储类、生命周期、作用域、链接域等 2.指针:指针概念、数组相关的指针、字符串指针、函数指针等 3.内存bug:内存越位、内存覆盖、使用非法内存、内存泄露等。 4.函数:自定义函数...
  • GNU Fortran和GNU C/C++可以互相编译对方语言代码,因此既可以像ifort那样和C/C++分别编译后链接,也可以只用其中一个完成编译链接; 使用gfortran编译C/C++文件时,.c文件默认使用c编译器,.cpp文件默认使用c++...
  • GCC 编译链接命令用法

    千次阅读 2016-06-17 14:16:36
    gcc 是编译最基本的命令,网上摘录其他一些信息整理,以备查阅
  • 如果使用 其他链接编译出现问题,或 编译出来的程序 运行存在问题,请试试更换 成 vc6 的链接器再次编译测试;注意,其他链接器需要安装运行库才能运行,有打包一些,在对应的链接器里面//-----------------------...
  • 1.最基本的编译文件方法 2.C++编译多个文件 makefile
  • 在Linux下使用GCC将源码编译成可执行文件的过程可以分解为4个步骤,分别是预处理(Prepressing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。一个简单的hello word程序编译过程如下: 1. 预处理 ...
  • GCC 编译 链接选项-I,-l,-L,-Wl:rpath

    千次阅读 2018-07-12 11:00:50
    -I,添加包含路径(大写i)-I 在编译时用,告诉编译器去哪个路径下找文件如:-I /home/hello/include表示将/home/hello/include目录作为第一个寻找头文件的目录。编译器的寻找顺序是:/home/hello/include--&gt...
  • 从官网下载libcurl之后,它内部已经包含了各个VC版本的工程文件(sln文件和vcproj文件)...在使用静态库时,却遇到了编译链接错误: 1>testcurl.obj : error LNK2001: unresolved external symbol __imp__curl_easy_i
  • OpenCV 3.0 程序编译链接错误

    千次阅读 2015-05-15 10:41:50
    问题描述 error: main.o: undefined reference to symbol ‘_ZN2cv6imreadERKNS_6StringEi’ ...问题描述里已经表述得很清楚了,需要链接libopencv_imgcodecs.so,这可能是和2.x版本的一处不同。
  • openssl安装以及gcc编译链接

    千次阅读 2013-12-10 17:16:27
    1.源代码安装时,源代码在window的共享目录下,mount到linux,在linux下make的时候出现 No rule to make target `../include/openssl/asn1.h', needed by `bntest.o',拷贝源代码在linux的自己的任意目录下就能make...
  • 编译链接中的-可重定位目标文件

    千次阅读 2016-07-09 10:25:34
    编译器编译后产生的目标文件是可重定位的程序模块,并不能直接运行,链接就是把目标文件和其他分别进行编译生成的程序模块(如果有的话)及系统提供的标准库函数连接在一起,生成可运行的可执行文件的过程。...
  • 1、把头文件及C文件编译成*.o的文件 一般的命令:  gcc -c -fPIC x.c  ... -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足
  • Linux程序编译链接动态库版本的问题

    千次阅读 2015-08-17 15:40:01
    在实际生产环境中,程序的编译和运行往往是分开的,但只要搞清楚这一系列过程中的原理,就不怕被动态库的版本搞晕。简单来说,按如下方式来做 编译动态库时指定-Wl,-soname,libxxx.so.a,设置soname为libxxx.so.a,...
  • gcc,g++编译链接有关的路径

    千次阅读 2014-04-21 13:13:05
    在Linux下编译链接c/c++程序时可能会遇到找不到头文件,找不到库文件,或者在

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 812,539
精华内容 325,015
关键字:

编译链接