精华内容
下载资源
问答
  • 编译链接原理

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

        对于源文件是怎么变成可执行程序的,当执行一个程序时,都做了那些处理,相信大家都比较好奇。在这里将简单介绍下程序的编译链接原理。
        在ANSI C的任何一种实现中,存在两种环境翻译环境和执行环境。翻译环境主要将源代码转化为可执行的机器指令。执行环境用于实际执行代码。在翻译环境中,主要进行编译和链接,一个程序在编译阶段主要进行了预处理、编译和汇编处理。下面将对各阶段进行分析。(环境:centos6.5)

     翻译环境  
        预处理  
            命令:gcc -E Main.c -o Main.i
            
       预处理阶段的主要作用:(文本操作,产生 *.o文件)
             a. 头文件的包含(处理#include预编译指令,将被包含的文件插入到预编译处理指令的位置)
         b. 注释的删除
         c. #define标识符的删除和替换
         e. 宏替换
         d.添加行号和文本标识,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能欧够显示行号 
         f. 保留所用的#program编译器指令,因为编译器需要使用它们


         编译   
             命令:gcc -S Main.c -o Main.s
       编译阶段的主要作用:(生成汇编代码)
            语法分析、词法分析、语义分析、符号汇总

             汇编
            命令:gcc -c test.c
            汇编阶段的主要作用:生成test.o目标文件 由汇编指令---> 二进制指令
                 形成符号表(将编译阶段汇总的符号形成一张符号表)
            可以使用readelf -s test.o查看符号表
              

        链接
          每个源代码模块独立地编译,然后按照要求将他们组装起来,这个组装过程就是链接。链接的主要内容就是把各个模块之间的相互引用部分处理好,使得各个模块之间能够正确的链接。
        主要作用:
            a.符号表的合并和符号表的重定位  
            将汇编阶段生成的符号表进行合并。可重定位目标文件之中用来存放变量和其入口地址的符号表,重定位是指在链接阶段连接器会查找符号表,当发现某个符号表存在没有决议的内存地址时,连接器就会查找所有符号表,一直发现这些这些尚未决议的符号变量的内存地址写进符号表。直达所有的符号变量都能够找到合法的内存地址时,链接阶段重定位完成。否则会出现链接错误。
            b.段表的合并
             在汇编阶段生成的Main.o为elf格式的文件,里面包含段表等信息,合并段表是将每个可执行文件中对应的各段信息进行合并。


            运行环境           

                 程序的执行环境:
               1. 程序必须载入内存。 在有操作系统的环境中,一般由操作系统完成。
               2. 程序的执行开始。接着便调用main函数
               3. 开始执行程序代码,这个时候程序将使用一个运行时堆栈,存储函数的局部变量和返回地址。程序也可以使用静态内容,存储以静态内存中的变量在程序的整个执行过程一直保留他们的值。
               4. 终止程序。 正常终止main函数,也有可能是意外终止。

             这里只是对编译和链接过程做了简单的介绍,以至于我们可以对源程序到可执行程序的过程有一个基本的认识。

                     





    展开全文
  • C/C++编译链接原理

    千次阅读 2012-03-20 12:29:52
    看了c++ primer,写过一些C++程序后,对其中的编译链接原理总是不明就里,想来这也难怪,因为平常都是在VS上,什么都是封装好了的,隐藏了太多的细节。本着自己一贯来对底层实现探究的兴趣,结合借鉴他人的想法,记...

    看了c++ primer,写过一些C++程序后,对其中的编译链接原理总是不明就里,想来这也难怪,因为平常都是在VS上,什么都是封装好了的,隐藏了太多的细节。本着自己一贯来对底层实现探究的兴趣,结合借鉴他人的想法,记下自己对C/C++编译链接原理的一些理解,要是能给看到此文章的你带来一丁点帮助就欣慰了。

    编译是把源文件经过预编译,优化,汇编翻译成机器语言的过程,这些机器语言代码数据以一定的格式COFF(Common Object File Format),OMF(Object Module Format),ELF(Executed linked Format),PE(Portable Executable)等存放于目标文件中。目标文件通常包含未解决符号表,导出符号表和地址重定向表。连接器具体说明如下:

     

    -----编译和链接大致流程-----

    编译器的工作不单单只有编译,事实上,它包括了从高级语言到机器语言的完整过程: 
    预编译-》编译-》汇编-》链接。

    1. 预编译 
      预编译过程主要是处理源代码文件中以#开始的预编译指令。主要处理规则如下: 
      1.1.将所有#define删除,并展开所有的宏定义。 
      1.2.处理所有条件编译指令,比如#ifdef、#else等。 
      1.3.处理#include,递归的将被包含的文件出入到该指令的位置。 
      1.4.删除所有注释。 
      1.5.添加行号和文件名标识,以便于编译器调试产生的行号消息和警告时显示的行号。 
      1.6.保留所有#pragma编译指令。#pragma指令是编译器参数。经过预编译之后,产生一个*.i文件。
    2. 编译 
      编译过程就是把预处理完成的*.i文件进行词法分析、语法分析、语义分析以及优化之后,产生相应的汇编代码文件。 
      2.1.词法分析利用扫描器和有限状态机算法,将源代码字符按照特定的字符标识分割成一系列记号。这些记号包含了以下几种分类:关键字、标识符、字面量(包括数字、字符串等)和特殊符号(加减乘除等)。 
      2.2.语法分析产生表达式语法树,但是不排查这个语句是否合法。 
      2.3.语义分析给语法树添加类型标识,并检查表达式是否合法。 
      2.4.中间代码生成。 
      2.5.目标代码生成和优化经过编译之后,产生一个汇编输出文件*.s文件。
    3. 汇编 
      汇编过程就是将汇编代码转变成机器代码文件*.o文件。这是个相对简单的过程,根据汇编指令和机器指令对照一一翻译就可以了。
    4. 链接 
      连接器将多个*.o文件彼此关联拼接到一起,最终产生一个可执行文件。分为静态链接和动态链接。

    -----编译和链接大致流程-----

     


     

    --------参考自http://blog.csdn.net/success041000/article/details/6714195----------

     源文件:A.cpp

         int n = 1;

         void FunA() {

             ++n;

         }

      目标文件:A.obj

         偏移量     内容     长度

         0x0000    n             4

         0x0004    FunA     ??

     注意:这只是说明,与实际目标文件的布局可能不一样,??表示长度未知,目标文件的各个数据可能不是连续的,也不一定是从0x0000开始。

     FunA函数的内容可能如下:

         0x0004  inc  DWORD  PTR[0x0000]

         0x00??  ret

         这时++n已经被翻译成inc DWORD PTR[0x0000],也就是说把本单元0x0000位置的一个DWORD(4字节)加1。


    源文件:B.cpp

         extern int n;

         void FunB() {

            ++n;

         }

         目标文件:B.obj

         偏移量     内容     长度

         0x0000    FunB     ??

           这里为什么没有n的空间呢,因为n被声明为extern,这个extern关键字就是告诉编译器n已经在别的编译单元里定义了,在这个单元里就不要定义了。由于编译单元之间是互不相关的,所以编译器就不知道n究竟在哪里,所以在函数FunB就没有办法生成n的地址,那么函数FunB中就是这样的:

         0x0000 inc DWORD PTR[????]

         0x00?? ret

          那怎么办呢?这个工作就只能由链接器来完成了。

          为了能让链接器知道哪些地方的地址没有填好(也就是????),那么目标文件中就要有一个表来告诉链接器,这个表就是“未解决符号表”,也就是unresolved symbol table。同样,提供n的目标文件也要提供一个“导出符号表”也就是exprot symbol table,来告诉链接器自己可以提供哪些地址。 

          到这里我们就已经知道,一个目标文件不仅要提供数据和二进制代码外,还至少要提供两个表:未解决符号表和导出符号表,来告诉链接器自己需要什么和自己能提供些什么。那么这两个表是怎么建立对应关系的呢?这里就有一个新的概念:符号。在C/C++中,每一个变量及函数都会有自己的符号,如变量n的符号就是n,函数的符号会更加复杂,假设FunA的符号就是_FunA(C++标准并未定义,这取决于编译器的具体实现)。

        A.obj的导出符号表

        符号            地址

        n                0x0000

        _FunA       0x0004

        A.obj的未解决符号表

       为空(因为它没有引用别的编译单元里的东西)

        B.obj的导出符号表

        符号             地址

        _FunB        0x0000

        B.obj的未解决符号表

        符号             地址

        n                  0x0001

          这个表告诉链接器,在本编译单元0x0001位置有一个地址,该地址不明,但符号是n。

          在链接的时候,链接在B.obj中发现了未解决符号,就会在所有的编译单元中的导出符号表去查找与这个未解决符号相匹配的符号名,如果找到,就把这个符号的地址填到B.obj的未解决符号的地址处。如果没有找到,就会报链接错误。在此例中,在A.obj中会找到符号n,就会把n的地址填到B.obj的0x0001处。 

           但是,这里还会有一个问题,如果是这样的话,B.obj的函数FunB的内容就会变成inc DWORD PTR[0x000](因为n在A.obj中的地址是0x0000),由于每个编译单元的地址都是从0x0000开始,那么最终多个目标文件链接时就会导致地址重复。所以链接器在链接时就会对每个目标文件的地址进行调整。在这个例子中,假如B.obj的0x0000被定位到可执行文件的0x00001000上,而A.obj的0x0000被定位到可执行文件的0x00002000上,那么实现上对链接器来说,A.obj的导出符号地地址都会加上0x00002000,B.obj所有的符号地址也会加上0x00001000。这样就可以保证地址不会重复。 

           既然n的地址会加上0x00002000,那么FunA中的inc DWORD PTR[0x0000]就是错误的,所以目标文件还要提供一个表,叫地址重定向,address redirect table。


    目标文件至少要提供三个表:未解决符号表,导出符号表和地址重定向表。

         (1)未解决符号表:列出了本单元里有引用但是不在本单元定义的符号及其出现的地址。

         (2)导出符号表:提供了本编译单元具有定义,并且可以提供给其他编译单元使用的符号及其在本单元中的地址。

         (3)地址重定向表:提供了本编译单元所有对自身地址的引用记录。 

          链接器的工作顺序:

          当链接器进行链接的时候,首先决定各个目标文件在最终可执行文件里的位置。然后访问所有目标文件的地址重定义表,对其中记录的地址进行重定向(加上一个偏移量,即该编译单元在可执行文件上的起始地址)。然后遍历所有目标文件的未解决符号表,并且在所有的导出符号表里查找匹配的符号,并在未解决符号表中所记录的位置上填写实现地址。最后把所有的目标文件的内容写在各自的位置上,再作一些另的工作,就生成一个可执行文件。

          说明:实现链接的时候会更加复杂,一般实现的目标文件都会把数据,代码分成好向个区,重定向按区进行,但原理都是一样的。明白了编译器与链接器的工作原理后,对于一些链接错误就容易解决了。


    下面是C/C++中一些相关的特性:

         extern:这就是告诉编译器,这个变量或函数在别的编译单元里定义了,也就是要把这个符号放到未解决符号表里面去(外部链接)。

         static:如果该关键字位于全局函数或者变量的声明前面,表明该编译单元不导出这个函数或变量,因些这个符号不能在别的编译单元中使用(内部链接)。如果是static局部变量,则该变量的存储方式和全局变量一样,但是仍然不导出符号。 

         默认链接属性:对于函数和变量,默认链接是外部链接,对于const变量,默认内部链接。

         外部链接的利弊:外部链接的符号在整个程序范围内都是可以使用的,这就要求其他编译单元不能导出相同的符号(不然就会报

    duplicated external symbols)。

         内部链接的利弊:内部链接的符号不能在别的编译单元中使用。但不同的编译单元可以拥有同样的名称的符号。

         为什么头文件里一般只可以有声明不能有定义:头文件可以被多个编译单元包含,如果头文件里面有定义的话,那么每个包含这头文件的编译单元都会对同一个符号进行定义,如果该符号为外部链接,则会导致duplicated external symbols链接错误。 

         为什么公共使用的内联函数要定义于头文件里:因为编译时编译单元之间互不知道,如果内联被定义于.cpp文件中,编译其他使用该函数的编译单元的时候没有办法找到函数的定义,因些无法对函数进行展开。所以如果内联函数定义于.cpp里,那么就只有这个.cpp文件能使用它。


    --------参考自http://blog.csdn.net/success041000/article/details/6714195----------


    以上只是大体概念上的讲述,设计具体环境的编译链接过程细节及文件输出格式等再做补充。



    展开全文
  • C++编译链接原理简介

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

    在实习的过程中,偶尔会在编译代码的时候出现莫名其妙的链接错误,或者更惨的是,编译链接通过了,运行的时候出现莫名其妙的coredump,查了半天原来是.a静态库更新了导致.h文件和.o文件不一致。

    受够了被这些错误支配的恐惧,所以决定补充一下这方面的知识。

    以下内容参考自网络。

    几个概念:

    1、编译:编译器对源文件进行编译,就是把源文件中的文本形式存在的源代码翻译成机器语言形式的目标文件的过程,在这个过程中,编译器会进行一系列的语法检查。如果编译通过,就会把对应的CPP转换成OBJ文件。

    2、编译单元:根据C++标准,每一个CPP文件就是一个编译单元。每个编译单元之间是相互独立并且互相不可知。

    3、目标文件:由编译所生成的文件,以机器码的形式包含了编译单元里所有的代码和数据,还有一些其他信息,如未解决符号表导出符号表地址重定向表等。目标文件是以二进制的形式存在的。

    我们知道,在预编译的时候,.h头文件会被复制、扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件,该.cpp文件作为一个编译单元独立编译。当编译器将一个工程里的所有.cpp文件以分离的方式编译完毕后,再由链接器进行链接成为一个可执行文件。

    编译器的工作过程:

    这里我们只关注下目标文件的生成。

    假设有一个A.cpp文件,如下定义:

        int n = 1;
    
        void FunA()
        {
            ++n;
        }
    它编译出来的目标文件A.obj就会有一个区域(或者说是段),包含以上的数据和函数,其中就有n、FunA,以文件偏移量形式给出可能就是下面这种情况:
    
    偏移量    内容    长度
    
    0x0000    n       4
    
    0x0004    FunA    ??
    

    说明:实际目标文件的布局可能不是这样,这里只是方便学习才这样表示,??表示长度未知,目标文件的各个数据可能不是连续的,也不一定是从0x0000开始。

    FunA函数的内容可能如下:

        0x0004 inc DWORD PTR[0x0000]
        0x00?? ret

    有另外一个B.cpp文件,定义如下:

        extern int n;
    
        void FunB()
        {
            ++n;
        }

    它对应的B.obj的二进制:

    偏移量    内容    长度
    
    0x0000    FunB    ??
    

    由于n被声明为extern,而extern关键字告诉编译器n已经在别的编译单元里定义了,在这个单元里不用定义。由于编译单元之间是互不相关的,所以编译器就不知道n究竟在哪里,所以在函数FunB中就没有办法生成n的地址,那么函数FunB中就是这样的:

    0x0000 inc DWORD PTR[????]
    
    0x00?? ret
    

    为了让各个编译单元结合起来,就需要链接器了。为了能让链接器知道哪些地方的地址没有填好(也就是还????),那么目标文件中就要有一个表来告诉链接器,这个表就是“未解决符号表”(unresolved symbol table)。同样,提供n的目标文件也要提供一个“导出符号表”(exprot symbol table),来告诉链接器自己可以提供哪些地址。

    因此,一个目标文件不仅要提供数据和二进制代码,还要提供两个表:未解决符号表和导出符号表,来告诉链接器自己需要什么和自己能提供些什么。

    那么这两个表是怎么建立对应关系的呢?

    在C/C++中,每一个变量及函数都会有自己的符号,如变量n的符号就是n,函数的符号会更加复杂,根据编译器不同而不同。

    A.obj的导出符号表为

    符号    地址
    
    n       0x0000
    
    _FunA   0x0004
    

    未解决符号为空。

    B.obj的导出符号表为

    符号    地址
    
    _FunB   0x0000
    

    未解决符号表为

    符号    地址
    
    n       0x0001
    

    这个表告诉链接器,在本编译单元0x0001位置有一个地址,该地址不明,但符号是n。

    在链接的时候,链接器在B.obj中发现了未解决符号,就会在所有的编译单元中的导出符号表去查找与这个未解决符号相匹配的符号名,如果找到,就把这个符号的地址填到B.obj的未解决符号的地址处。如果没有找到,就会报链接错误。在此例中,在A.obj中会找到符号n,就会把n的地址填到B.obj的0x0001处。

    但是,如果是这样的话,B.obj的函数FunB的内容就会变成

    inc DWORD PTR[0x000](因为n在A.obj中的地址是0x0000)
    

    如果每个编译单元的地址都是从0x0000开始,那么最终多个目标文件链接时就会导致地址重复。所以链接器在链接时就会对每个目标文件的地址进行调整。比如B.obj的0x0000被定位到可执行文件的0x00001000上,而A.obj的0x0000被定位到可执行文件的0x00002000上,这样就可以保证地址不会重复。为实现这一点,目标文件还要提供一个表,叫地址重定向表(address redirect table)。

    总结:

    目标文件至少要提供三个表:未解决符号表,导出符号表和地址重定向表。

    未解决符号表:列出了本单元里有引用但是不在本单元定义的符号及其出现的地址。

    导出符号表:提供了本编译单元具有定义,并且可以提供给其他编译单元使用的符号及其在本单元中的地址。

    地址重定向表:提供了本编译单元所有对自身地址的引用记录。

    链接器的工作顺序:

    当链接器进行链接的时候,首先决定各个目标文件在最终可执行文件里的位置。然后访问所有目标文件的地址重定义表,对其中记录的地址进行重定向(加上一个偏移量,即该编译单元在可执行文件上的起始地址)。然后遍历所有目标文件的未解决符号表,并且在所有的导出符号表里查找匹配的符号,并在未解决符号表中所记录的位置上填写实现地址。最后把所有的目标文件的内容写在各自的位置上,再作一些其他工作,就生成一个可执行文件。

    说明:实现链接的时候会更加复杂,一般实现的目标文件都会把数据,代码分成好向个区,重定向按区进行,但原理都是一样的。

    几个经典的链接错误

    unresolved external link..
    

    这个很显然,是链接器发现一个未解决符号,但是在导出符号表里没有找到对应的项。

    解决方案就是在某个编译单元里提供这个符号的定义。(注意,这个符号可以是一个变量,也可以是一个函数),也可以看看是不是有什么该链接的文件没有链接。

    duplicated external simbols...
    

    这个则是导出符号表里出现了重复项,因此链接器无法确定应该使用哪一个。这可能是使用了重复的名称,也可能有别的原因。

    C/C++针对这些而提供的特性:

    extern:告诉编译器,这个符号在别的编译单元里定义,也就是要把这个符号放到未解决符号表里去。(外部链接)

    static:如果该关键字位于全局函数或者变量的声明的前面,表明该编译单元不导出这个函数/变量的符号。因此无法在别的编译单元里使用。(内部链接)。如果是static局部变量,则该变量的存储方式和全局变量一样,但是仍然不导出符号。

    默认链接属性:对于函数和变量,默认外部链接,对于const变量,默认内部链接。(可以通过添加extern和static改变链接属性)

    外部链接的利弊:外部链接的符号,可以在整个程序范围内使用(因为导出了符号)。但是同时要求其他的编译单元不能导出相同的符号(不然就是duplicated external simbols)

    内部链接的利弊:内部链接的符号,不能在别的编译单元内使用。但是不同的编译单元可以拥有同样名称的内部链接符号。

    一些问题的解答

    为什么头文件里一般只可以有声明不能有定义?

    头文件可以被多个编译单元包含,如果头文件里有定义,那么每个包含这个头文件的编译单元就都会对同一个符号进行定义,如果该符号为外部链接,则会导致duplicated external simbols。因此如果头文件里要定义,必须保证定义的符号只能具有内部链接。

    为什么类的静态变量不可以就地初始化?

    所谓就地初始化就是类似于这样:

        class A
        {
            static char msg[] = "aha";
        };
    

    由于class的声明通常是在头文件里,如果允许这样做,其实就相当于在头文件里定义了一个非const变量。

    为什么公共使用的内联函数要定义于头文件里?

    因为编译时编译单元之间互相不知道,如果内联函数被定义于.cpp文件中,编译其他使用该函数的编译单元时没有办法找到函数的定义,因此无法对函数进行展开。所以说如果内联函数定义于.cpp文件里,那么就只有这个cpp文件可以使用这个函数。

    头文件里的内联函数被拒绝会怎样?

    记住,内联只是给编译器的一个建议,如果定义于头文件里的内联函数被拒绝,那么编译器会自动在每个包含了该头文件的编译单元里定义这个函数并且不导出符号。

    展开全文
  • 在平常的应用程序开发过程中,我们很少需要关注编译链接的过程,因为通常都是在集成的开发环境下运行,因此一般编译链接都是一步完成,通常将这种编译和连接合并到一起的过程称为构建。这样虽然简便,但是在这...

    总述:

            在平常的应用程序开发过程中,我们很少需要关注编译和链接的过程,因为通常都是在集成的开发环境下运行,因此一般编译和链接都是一步完成,通常将这种编译和连接合并到一起的过程称为构建。这样虽然简便,但是在这整个过程中,有时出现问题时,我们只能看到问题的表现,而很难看清本质性问题,所以对于这些一步完成的操作背后到底是怎样的,我们需要深入了解,方便在以后遇到问题能后看清本质,快速解决。为什么要对源文件进行编译链接生成最后的可执行文件呢?一是机器只识别0/1代码,二是源文件在磁盘上存储,要运行源文件就必须将源文件转为机器可识别的二进制文件并将转换后的文件加载到内存中才可以。那么首先是对于编译链接原理的背后所作的事情的一个简单了解;

           程序的运行过程分为两大阶段,编译阶段和链接阶段,同时编译阶段又划分为三步预编译、编译和汇编,再逼那一阶段完成时,进行链接,那么具体每部所做的内容如下:

    一、预编译(生成.i文件)

    1.     宏替换(删除#define,并且展开所有的宏定义)
    2.     递归展开头文件(处理#include预编译指令,将包含的文件插入到该预编译指令的位置)
    3.     删除预编译指令(处理所有的条件预编译指令,例如"#if","#endif","#ifdef","dlif","#else"的等)
    4.     删除注释(删除”//“和"/**/")
    5.     添加行号和文件标识
    6.     保留#progma

    二、编译(生成.s文件)

           例如:int sum(int a,int b,int c = 10);

                       int sum(int a,int b = 20,int c);

                      int sum(int a,int b,int c);

         这三行代码是对sum函数的声明,在进行语法分析的时候第二行是错误的,因为函数的默认值是从左向右依次进行赋值的。但在进行到语义分析时是正确的,结合上下文进行分析,第一句已经对c进行了默认值,在第二句进行分析时,c是有默认值的,这个值是10,然后再到b的默认值。所以是正确的。 

    1.     词法分析 ;例如:int 1a = 1;(这里是1不是小写的’L‘)//错误。定义变量只能以字母或下划线开头
    2.     语法分析;例如:int a = 10;delete a;  //错误 在编译阶段,会识别到delete后必须是指针,而这里并不是指针,
    3.     语义分析(结合上下文进行分析);     
    4.     代码优化

    三、汇编(生成.o文件,称为可重定位的二进制文件)

     在汇编阶段,将代码翻译为二进制指令后,通过在Linux操作系统下,对汇编后的文件进行查看,得出,在汇编完成后还有以下事情未进行处理: 

         (1)弱符号位置未进行处理(2)虚拟地址以及虚拟位移未进行处理(3)符号表中的外部符号进行处理

    1.    将 指令代码翻译成二进制指令

    四、链接(生成.exe文件,称为可执行的二进制文件)

    1.     合并段(相同段之间)和符号表
    2.     进行符号解析:在符号引用的地方找到符号定义的地方
    3.     分配地址和空间
    4.     符号的重定位

    编译阶段:

     一、.o文件

            编译阶段经过预编译、编译和汇编处理后生成一个.o文件(以Linux系统为例),又编译器编译源代码后生成的文件叫做目标文件。则目标文件就是源代码编译后但未进行连接的那些中间文件(windows下的.obj和Linux下的.o),它跟可执行文件的内容和结构很相似,所以一般和可执行文件采用同一种格式存储。也就是从结构上来说,目标文件是已经变异后的可执行文件,知识没有经过链接阶段,其中有些符号或者地址没有被调整。

           二、目标文件的内容以及存放

            那么目标文件中至少有编译后的机器指令代码、数据。当然,除了这些内容外,目标文件中还包含了链接时所需要的一些信息,例如符号表、调试信息、字符串等。一般目标文件将这些信息按照不同的属性按“段”的形式进行存储。程序源代码编译后的机器指令经常被放在代码段里,即“.text”中。全局变量和局部静态变量数据经常放在数据段,即".data"中。未初始化的全局变量和局部静态变量放在.bss段中。

            四、.bss段

            .bss段存储的是未定义的全局变量和局部静态变量。但.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,他并没有内容,所以他在文件中也不占据空间。在这里我们引入强弱符号(c语言中,只关心全局变量)的概念:

            强符号:已初始化的全局变量;

            弱符号:未初始化的全局变量;

            强弱符号的使用规则:

    1. 两强符号:重定义错误
    2. 一强一弱符号:选择强符号
    3. 两弱符号:选取字节数较大的

            三、指令段和数据段

            在编译阶段结束会,生成可重定位的二进制文件即目标文件,将文件中的指令数据等信息分别按照属性存储在虚拟地址空间中,数据区域对进程来说是可读写的,而指令段对与进程而言只是可读的,所以这两个区域的权限是可读写和只读。这样就会防止指令被有意无意的篡改,同时当程序运行多个该程序的副本时,它们的指令是相同的,所以内存中只须要保存一份该程序的指令部分。并且分开存储有利于提高CPU的缓存命中率。 

            4G的虚拟地址空间如图所示:

    链接阶段:

           一、 在编译阶段完成后生成.o的目标文件,进入到链接阶段,对于链接器来说,整个链接过程就是将几个输入目标文件加工合并成一个输出文件。这里的输入文件是目标文件即.o文件,输出文件是可执行的二进制文件。链接后的文件存储和目标文件的存储一样,都是将不同的信息属性存放到对应的段中,唯一不同的是可执行的二进制文件会对一些符号进行解析,调整一些地址等;

            那么,在链接阶段具体都要做些什么呢?

           1.符号表和段合并:将相同性质的段合并到一起

          2.符号解析:在符号引用的地方找到符号定义的地方

          3.分配地址和空间

          4.符号的重定位: 处理虚假偏移量

    运行原理:

            在编译链接阶段结束后,也就是生成了 可执行的二进制文件;但该文件并不能直接进行运行,因为此时的文件并未在内存中,也就是说,操作系统在运行一个程序时,需要指令和数据,并且必须将所要执行的程序加载到内存上;

    那么,在运行时,需要做以下的事情:

    1. 创建虚拟地址和物理内的映射结构体;按照段页式进行映射,以4K大小对齐;
    2. LOAD加载器,将指令和数据加载到内存中;
    3. 将第一行指令的地址写入PC寄存器中;

           ps:本文中的测试,均在Linux下进行;

    展开全文
  • 后来了解了编译链接的一些原理,总算有一个比较清晰的理解,整理出来和大家分享。若有不对之处,还望斧正!谢谢! 五.总结 1.头文件并不参加链接编译。编译器第一步要做的就是简单的把头文件在包含它
  • 编译原理视频链接

    2014-06-18 14:23:54
    编译原理视频,也是我找到你个人觉得蛮好的视频,我听的就是这个.我整理好了ppt.
  • 最近在学习编译原理,对于程序的一般编译链接、执行过程很是疑惑,所以在网上查阅相关资料,并进行了整理。
  • gcc程序的编译过程和链接原理

    万次阅读 多人点赞 2017-10-24 17:02:07
    一、C/C++文件的编译过程:先来看一下gcc的使用方法和常用选项 提示:gcc --help Ⅰ、使用方法:gcc [选项] 文件名Ⅱ、常用选项: 选项 含义 -v 查看gcc编译器的版本,显示gcc执行时的详细过程 -o ...
  • C++编译链接原理

    2016-11-25 15:20:11
    C++编译原理 1、编译:编译器对源文件进行编译,就是把源文件中的文本形式存在的源代码翻译成机器语言形式的目标文件的过程,在这个过程中,编译器会进行一系列的语法检查。如果编译通过,就会把对应的CPP转换成...
  • 编译原理

    千次阅读 2014-09-11 23:00:28
    编译过程就是把预处理的文件进行一系列此法分析,语法分析,语义分析以及优化后生产相应的汇编代码文件。主要分为5部分,分别是:词法分析、语法分析、语义分析、中间语言生产和...本文图示介绍编译原理的整个过程。
  • 程序编译链接原理理解

    千次阅读 2016-08-11 19:51:46
    本书主要介绍系统软件的运行机制和原理,涉及在Windows和Linux两个系统平台下,一个应用程序在编译链接、和运行时所做的事,具体如下: 1.Windows和Linux操作系统下各自的可执行文件、目标文件格式? 2.普通的C/...
  • VC++程序编译链接原理与过程

    千次阅读 2010-10-16 15:44:00
    我们在EX10这个工程中,选择菜单中【Build】→【Rebuild All】,重新编译所有的工程文件,可以看到如下输出: <br />  <br /> 从这个输出中,我们可以看到可执行程序EX10.exe的产生,经过了两个...
  • 编译原理中文

    2019-07-22 18:01:38
    资源名称:编译原理中文资源截图: 资源太大,传百度网盘了,链接在附件中,有需要的同学自取。
  • 编译原理基础

    2019-07-22 17:29:32
    资源名称:编译原理基础资源截图: 资源太大,传百度网盘了,链接在附件中,有需要的同学自取。
  • 编译基本原理

    千次阅读 2013-10-12 16:01:35
    也没曾想过我的程序为什么能跑起来,也不曾知道有编译链接两个过程等等,只知道我按语法来,我的程序就能正常跑,如果不是预期的结果,通过查看分析代码,慢慢找到Bug根源。直到Windows平台编写代码,
  • GCC编译原理——链接

    千次阅读 2018-07-18 22:45:50
    链接可以执行与编译时,在源代码翻译成机器代码时;也可执行与加载时,也就是在程序被加载器加载到内存并执行时;还可以执行与运行时,也就是由应用程序来执行。 连接器在软件开发中扮演一个关键的角色,它们使得...
  • 从这个输出中,我们可以看到可执行程序EX10.exe的产生,经过了两个步骤:首先,C++编译器对工程中的三个源文件fish.cpp、animal.cpp单独进行编译(Compiling…)。在编译时,先由预处理器对预处理指令(#...
  • 编译原理 华保健

    2018-06-15 11:51:25
    华保健老师的编译原理,讲的非常好,通俗易懂。百度云链接,永久有效
  • 先上一张图吧,完美表示出了,一个编辑好的程序变成可运行文件经过的过程(可以把编译和汇编统称为编译)。 一、预处理(预编译编译预处理) 主要处理源代码文件中的以“#”开头的预编译指令 1.删除所有的#define...
  • 交叉编译原理

    2020-05-03 11:04:29
    使用本机器的编译器,将源代码编译链接成为一个可以在本机器上运行的程序。这就是正常的编译过程. 交叉编辑 就是在一个平台(如PC)上生成另外一个平台(Android、iOS或者其他嵌入式设备)的可执行程序,相较于...
  • 但是其中的原理是什么呢?看到这篇文章的同学肯定都有这样的疑惑,让我们一起来了解一下。   编译过程细节:   test.c(文本格式c程序) -> (预处理器cpp)-> test.i(文本格式c程序) -> (编译器 ccl) -
  • 十分清晰的技术讲解,对于UNIX平台下进行开发的人员有直观的帮助。大家自己可以作为参考。
  • 编译原理书籍推荐

    千次阅读 多人点赞 2018-09-28 13:44:20
    大学课程为什么要开设编译原理呢?这门课程关注的是编译器方面的产生原理和技术问题,似乎和计算机的基础领域不沾边,可是编译原理却一直作为大学本科的必修课程,同时也成为了研究生入学考试的必考内容。编译原理及...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 149,586
精华内容 59,834
关键字:

编译链接原理