精华内容
下载资源
问答
  • 编译过程符号表重定位问题:转载至:点击打开链接 对于代码的编译问题千头万绪从何说起呢,首先来说一下计算机是如何处理应用程序的,实质上应用程序是通过操作系统来应用机器指令操控硬件设施完成各种任务的,...

    编译过程和符号表重定位问题:转载至:点击打开链接

    对于代码的编译问题千头万绪从何说起呢,首先来说一下计算机是如何处理应用程序的,实质上应用程序是通过操作系统来应用机器指令操控硬件设施完成各种任务的,就从编译的环节开始谈起吧,众所周知,程序开发人员所写的代码实际上计算机是没有办法去认识的,那么就必须通过编译将其转换为计算机可以认识的机器指令,在有操作系统根据具体指令从硬件上分配内存处理程序段。以下从预编译,编译,汇编,链接,来简单的说一下程序的编译过程。

    2.1编译预处理

    在这个阶段主要是宏定义的展开,以及头文件的递归处理,即展开所有的以#开头的编译命令,删除所有注释,添加行号和文件名标识,保留所有的#program

    2.2编译阶段

    将程序代码段按字符流格式进行切割,处理,主要是词法分析,语法分析,语义分析以及优化等阶段,编译完成后生成中间代码。

    2.3汇编

    将编译后的中间代码通过汇编器模块生成计算机能够识别的机器指令用以操控硬件设施生成目标代码(可重定位目标代码)。

    2.4链接

    通过链接器模块将各种目标代码以及库文件(*.lib文件),资源文件(*,rec)进行链接处理最终生成可以执行的*.exe文件。

    2.5重定位问题

    通过一个例子来看:假如我们有两个头文件和两个源文件分别叫做function1.h和function2.h以及function1.cpp和function2.cpp文件其中function1.h内容如下

     

    Function1.h

    #ifndef   _FUNCTION1_H

    #define   _FUNCTION1_H

    Int g_val;

    Int Add(int m, int n);

    #endif

     

    Function1.cpp

    g_val=10;

    Int Add(int m, int n)

    {

    Return m+n;

    }

    Function2.cpp其中包含了main函数内容如下

    #include “function1.h”

    Int main()

    {

    Int  l_valfri=3;

    Int  l_valsec=4;

    g_val=14;

    Int result=Add(l_valfri,l_valsec);

    Return 0;

    }

    对于这样的代码编译器在编译function2.cpp时对于外部符号g_val 和外部函数Add该如何决议呢,这里又会涉及到可重定位文件中的符号表问题。

    其实在可重定位目标文件之中会存在一个用来放置变量和其入口地址的符号表,当编译过程中能够找到该符号的定义时就将该符号入口地址更新到符号表中否则就对该符号的地址不做任何决议一直保留到链接阶段处理。通过两个例子来看符号表的结构。

    在编译过程中function1.cpp文件的可重定位目标文件中的符号表如下

    变量名

    内存地址

    g_val

    0x100

    Add

    0x200

     

     

    为什么可以做到对于符号g_val和Add分配内存地址呢,因为在编译阶段就能够在function1.cpp文件中找到他们的定义,所以能够进行明确的内存地址分配。

    再来看看function2.cpp所生成的可重定位目标文件的结构:

    变量名

    内存地址

    g_val

    0x00

    Add

    0x00

    为什么会出现这样的状况。因为在编译阶段虽然可以看到这些符号变量的声明,但却找不到他们的定义所以编译器陷入了一个决而未决的境地。

    将包含文件展开时,function2.cpp大概会是这个样子很明显只有符号变量的声明但是没有定义。

    #ifndef   _FUNCTION1_H

    #define   _FUNCTION1_H

    Int g_val;

    Int Add(int m, int n);

    #endif

     

     

    Int main()

    {

    Int  l_valfri=3;

    Int  l_valsec=4;

    g_val=14;

    Int result=Add(l_valfri,l_valsec);

    Return 0;

    }

     

    先将他们存放在符号表中但却不去为他们进行内存关联一直等到链接阶段在进行处理。

    重定位发生于目标代码链接阶段,在链接阶段链接器就会查找符号表,当他发现了function2.cpp的符号表之中任然有没有决议的内存地址时,链接器就会查找所有的目标代码文件,一直到他找到了function1.cpp所生成的目标代码文件符号表时发现了这些没有决议的符号变量的真正内存地址,这是function2.cpp所生成的目标代码文件就会更新它的符号表,将这些尚未决议的符号变量的内存地址写进其符号表中。

    更新之后的function2.obj文件符号表

    变量名

    内存地址

    g_val

    0x100

    Add

    0x200

     

     

     

    当所有的符号变量都能够找到合法的内存地址时,链接阶段重定位完成。


    静态和动态链接:转载至点击打开链接

    即使是最简单的HelloWorld的程序,它也要依赖于别人已经写好的成熟的软件库,这就是引出了一个问题,我们写的代码怎么和别人写的库集成在一起,也就是链接所要解决的问题。

    首先看HelloWorld这个例子:
    [cpp] view plain copy
    1. // main.c  
    2.   1 #include <stdio.h>  
    3.   2  
    4.   3 int main(int argc, char** argv)  
    5.   4 {  
    6.   5         printf("Hello World! argc=%d\n", argc);  
    7.   6         return 0;  
    8.   7 }  

    HelloWorld的main函数中引用了标准库提供的printf函数。链接所要解决的问题就是要让我们的程序能正确地找到printf这个函数。
    解决这个问题有两个办法:一种方式是在生成可执行文件的时候,把printf函数相关的二进制指令和数据包含在最终的可执行文件中,这就是静态链接;另外一种方式是在程序运行的时候,再去加载printf函数相关的二进制指令和数据,这就是动态链接。
    每个源文件都会首先被编译成目标文件,每个目标文件都提供一些别的目标文件需要的函数或者数据,同时又从别的目标文件中获得一些函数或者数据。因此,链接的过程就是目标文件间互通有无的过程。本文根据《程序员的自我修养》一书中关于静态和动态链接总结而成,欢迎指正并推荐阅读原书。

    静态链接

    静态链接就是在生成可执行文件的时候,把所有需要的函数的二进制代码都包含到可执行文件中去。因此,链接器需要知道参与链接的目标文件需要哪些函数,同时也要知道每个目标文件都能提供什么函数,这样链接器才能知道是不是每个目标文件所需要的函数都能正确地链接。如果某个目标文件需要的函数在参与链接的目标文件中都找不到的话,链接器就报错了。
    目标文件中有两个重要的接口来提供这些信息:一个是符号表,另外一个是重定位表。利用Linux中的readelf工具就可以查看这些信息。
    首先我们用命令gcc -c -o main.o main.c 来编译上面main.c文件来生成目标文件main.o。然后我们用命令readelf -s main.o来查看main.o中的符号表:
    [plain] view plain copy
    1. Symbol table '.symtab' contains 11 entries:  
    2.    Num:    Value  Size Type    Bind   Vis      Ndx Name  
    3.      0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND  
    4.      1: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c  
    5.      2: 00000000     0 SECTION LOCAL  DEFAULT    1  
    6.      3: 00000000     0 SECTION LOCAL  DEFAULT    3  
    7.      4: 00000000     0 SECTION LOCAL  DEFAULT    4  
    8.      5: 00000000     0 SECTION LOCAL  DEFAULT    5  
    9.      6: 00000000     0 SECTION LOCAL  DEFAULT    7  
    10.      7: 00000000     0 SECTION LOCAL  DEFAULT    8  
    11.      8: 00000000     0 SECTION LOCAL  DEFAULT    6  
    12. <strong>     9: 00000000    36 FUNC    GLOBAL DEFAULT    1 main  
    13.     10: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf</strong>  

    我们重点关注最后两行,从中可以看到main.o中提供main函数(Type列为FUNC,Ndx为1表示它是在本目标文件中第1个Section中),同时依赖于printf函数(Ndx列为UND)。

    因为在编译main.c的时候,编译器还不知道printf函数的地址,所以在编译阶段只是将一个“临时地址”放到目标文件中,在链接阶段,这个“临时地址”将被修正为正确的地址,这个过程叫重定位。所以链接器还要知道该目标文件中哪些符号需要重定位,这些信息是放在了重定位表中。很明显,在main.o这个目标文件中,printf的地址需要重定位,我们还是用命令readelf -r main.o来验证一下,这些信息是保存在.rel.textSection中:
    [plain] view plain copy
    1. Relocation section '.rel.text' at offset 0x400 contains 2 entries:  
    2.  Offset     Info    Type            Sym.Value  Sym. Name  
    3. 0000000a  00000501 R_386_32          00000000   .rodata  
    4. 00000019  00000a02 R_386_PC32        00000000   printf  
    那么既然main.o依赖于printf函数,你可能会问,printf是在哪个目标文件里面?printf函数是标准库的一部分,在Linux下静态的标准库libc.a位于/usr/lib/i386-linux-gnu/中。你可以认为标准库就是把一些常用的函数的目标文件打包在一起,用命令ar -t libc.a可以查看libc.a中的内容,其中你就可以发现printf.o这个目标文件。在链接的时候,我们需要告诉链接器需要链接的目标文件和库文件(默认gcc会把标准库作为链接器输入的一部分)。链接器会根据输入的目标文件从库文件中提取需要目标文件。比如,链接器发现main.o会需要printf这个函数,在处理标准库文件的时候,链接器就会把printf.o从库文件中提取处理。当然printf.o依赖的目标文件也很被一起提取出来。库中其他目标文件就被舍弃掉,从而减小了最终生成的可执行文件的大小。

    知道了这些信息后,链接器就可以开始工作了,分为两个步骤:1)合并相似段,把所有需要链接的目标文件的相似段放在可执行文件的对应段中。2)重定位符号使得目标文件能正确调用到其他目标文件提供的函数。
    用命令gcc -static -o helloworld.static main.c来编译并做静态链接,生成可执行文件helloworld.static。因为可执行文件helloworld.static已经是链接好了的,所以里面就不会有重定位表了。命令 readelf -S helloworld.static | grep .rel.text将不会有任何输出(注:-S是打印出ELF文件中的Sections)。经过静态链接生成的可执行文件,只要装载到了内存中,就可以开始运行了。

    动态链接

    静态链接看起来很简单,但是有些不足。其中之一就对磁盘空间和内存空间的浪费。标准库中那些函数会被放到每个静态链接的可执行文件中,在运行的时候,这些重复的内容也会被不同的可执行文件加载到内存中去。同时,如果静态库有更新的话,所有可执行文件都得重新链接才能用上新的静态库。动态链接就是为了解决这个问题而出现的。所谓动态链接就是在运行的时候再去链接。理解动态链接需要从两个角度来看,一是从动态库的角度,而是从使用动态库的可执行文件的角度。

    从动态库的角度来看,动态库像普通的可执行文件一样,有其代码段和数据段。为了使得动态库在内存中只有一份,需要做到不管动态库装载到什么位置,都不需要修改动态库中代码段的内容,从而实现动态库中代码段的共享。而数据段中的内容需要做到进程间的隔离,因此必须是私有的,也就是每个进程都有一份。因此,动态库的做法是把代码段中变化的部分放到数据段中去,这样代码段中剩下的就是不变的内容,就可以装载到虚拟内存的任何位置。那代码段中变化的内容是什么,主要包括了对外部函数和变量的引用。
    我们来看一个简单的例子吧,假设我们要把下面的代码做成一个动态库:
    [plain] view plain copy
    1.  1 #include <stdio.h>  
    2.  2 extern int shared;  
    3.  3 extern void bar();  
    4.  4 void foo(int i)  
    5.  5 {  
    6.  6   printf("Printing from Lib.so %d\n", i);  
    7.  7   printf("Printing from Lib.so, shared %d\n", shared);  
    8.  8  
    9.  9   bar();  
    10. 10   sleep(-1);  
    11. 11 }  
    用命令gcc -shared -fPIC -o Lib.so Lib.c将生成一个动态库Lib.so(-shared是生成共享对象,-fPIC是生成地址无关的代码)。该动态库提供(导出)一个函数foo,依赖(导入)一个函数bar,和一个变量shared。
    这里我们需要解决的问题是如何让foo这个函数能正确地引用到外部的函数bar和shared变量?程序装载有个特性,代码段和数据段的相对位置是固定的,因此我们把这些外部函数和外部变量的地址放到数据段的某个位置,这样代码就能根据其当前的地址从数据段中找到对应外部函数的地址(前提是谁能帮忙在数据段中填上这个外部函数的正确地址,下面会讲)。动态库中外部变量的地址是放在.got(global offset table)中,外部函数的地址是放在了.got.plt段中。
    如果你用命令readelf -S Lib.so | grep got将会看到Lib.so中有这样两个Section。他们就是分别存放外部变量和函数地址的地方。
    [plain] view plain copy
    1. [20] .got              PROGBITS        00001fe4 000fe4 000010 04  WA  0   0  4  
    2. [21] .got.plt          PROGBITS        00001ff4 000ff4 000020 04  WA  0   0  4  
    到此为止,我们知道了动态库是把地址相关的内容放到了数据段中来实现地址无关的代码,从而使得动态库能被多个进程共享。那么接着的问题就谁来帮助动态库来修正.got和.got.plt中的地址。

    那么我们就从动态链接器的角度来看看吧!

    静态链接的可执行文件在装载进入内存后就可以开始运行了,因为所有的外部函数都已经包含在可执行文件中。而动态链接的可执行文件中对外部函数的引用地址在生成可执行文件的时候是未知的,所以在这些地址被修正前是动态链接生成的可执行文件是不能运行的。因此,动态链接生成的可执行文件运行前,系统会首先将动态链接库加载到内存中,动态链接器所在的路径在可执行文件可以查到的。

    还是以前面的helloworld为例,用命令gcc -o helloworld.dyn main.c来以动态链接的方式生成可执行文件。然后用命令readelf -l helloworld.dyn | grep interpreter可以看到动态链接器在系统中的路径。
    [plain] view plain copy
    1. [Requesting program interpreter: /lib/ld-linux.so.2]  
    当动态链接器被加载进来后,它首先做的事情就是先找到该可执行文件依赖的动态库,这部分信息也是在可执行文件中可以查到的。用命令readelf -d helloworld.dyn,可以看到如下输出:
    [plain] view plain copy
    1. Dynamic section at offset 0xf28 contains 20 entries:  
    2.   Tag        Type                         Name/Value  
    3.  0x00000001 (NEEDED)                     Shared library: [libc.so.6]  
    或者用命令ldd helloworld.dyn,可以看到如下输出:
    [plain] view plain copy
    1. linux-gate.so.1 =>  (0x008cd000)  
    2. libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0x00a7a000)  
    3. /lib/ld-linux.so.2 (0x0035d000)  
    都表明该可执行文件依赖于libc.so.6这个动态库,也就是C语言标准库的动态链接版本。如果某个库依赖于别的动态库,它们也会被加载进来直到所有依赖的库都被加载进来。

    当所有的库都被加载进来以后,类似于静态链接,动态链接器从各个动态库中可以知道每个库都提供什么函数(符号表)和哪些函数引用需要重定位(重定位表),然后修正.got和.got.plt中的符号到正确的地址,完成之后就可以将控制权交给可执行文件的入口地址,从而开始执行我们编写的代码了。
    可见,动态链接器在程序运行前需要做大量的工作(修正符号地址),为了提高效率,一般采用的是延迟绑定,也就是只有用到某个函数才去修正.got.plt中地址,具体是如何做到延迟绑定的,推荐看《程序员的自我修养》一书。

    小结

    链接解决我们写的程序是如何和别的库组合在一起这个问题。每个参与链接的目标文件中都提供了这样的信息:我有什么符号(变量或者函数),我需要什么符号,这样链接器才能确定参与链接的目标文件和库是否能组合在一起。静态链接是在生成可执行文件的时候把需要的所有内容都包含在了可执行文件中,这导致的问题是可执行文件大,浪费磁盘和内存空间以及静态库升级的问题。动态链接是在程序运行的时候完成链接的,首先是动态链接器被加载到内存中,然后动态链接器再完成类似于静态链接器的所做的事情。

    展开全文
  • 编译原理=符号表的认识

    千次阅读 2018-06-23 15:23:06
    编译原理 符号表 符号表的每一项(入口)包含两大栏: 名字栏: 也称主栏,关键字栏. 信息栏: 记录相应的不同属性,分为若干子栏. 对符号表的操作: 填入名称. 查找名字. 访问信息. 填写修改信息. 删除. 对符号表...
    • 编译原理 符号表
      符号表的每一项(入口)包含两大栏:
      名字栏: 也称主栏,关键字栏.
      信息栏: 记录相应的不同属性,分为若干子栏.
      对符号表的操作:
      填入名称.
      查找名字.
      访问信息.
      填写修改信息.
      删除.
      对符号表进行操作的时机:
      定义性出现.
      使用性出现.
      按名字的不同种属建立多张符号表,如常数表,变量名表,过程名表,…
      符号的组织方式:
      1.安排各项各栏的存储单元为固定长度.
      2.用间接方式安排各栏存储单元.
      符号表的存放次序:
      1.把每一项置于连续K存储单元中,构成一张K*N的表.
      2.把整个符号表分成m个子表,如T1,T2,T3..Tm,每个子表包含有N项.
      符号表的信息栏中登记了每个名字的有关性质:
      类型:整,实或布尔等.
      种属:简单变量,数组,过程等.
      大小:长度,即所需的存储单元字数.
      相对数:指分配给该名字的存储单元的相对地址.
      PL语言编译程序的符号表.
      1.表格的定义:
      名字表 : 登记程序中出现的各种名字及属性.
      程序体表:
      层次显示表: 描述正在处理的各种嵌套层,对程序体表进行管理.
      数组信息表
      中间代码表: 用于存放编译程序所产生的每条中间代码.
    展开全文
  • 编译原理之符号表

    千次阅读 2019-06-20 12:24:56
    文章目录一、符号表的作用是什么1. 收集符号属性2. 上下文语义的合法性检查的依据3. 作为目标代码生成阶段地址分配的依据二、符号表的组织方式1. 符号表分为几大栏,主键是什么?2. 什么是各项,各栏等长,说明利弊?...

    一、符号表的作用是什么

    1. 收集符号属性

    • 例如,编译程序分析到下述两个说明语句
    int A;
    float B[5];
    

    则在符号表中收集到关于符号A的属性是一个整型变量,关于符号B的属性是具有5个浮点型元素的一维数组。

    2. 上下文语义的合法性检查的依据

    同一个标识符可能在程序的不同地方出现,而有关该符号的属性是在这些不同情况下收集的。特别是在多趟编译及程序分段编译(在PASCAL及C中以文件为单位)的情况下,更需检查标识符属性在上下文中的一致性和合法性。通过符号表中属性记录可进行相应上下文的语义检查。

    • 例如,在一个C语言程序中出现
    int i [3,5]; //定义整型数组i
    float i[4,2]; //定义实型数组i,重定义冲突
    

    3. 作为目标代码生成阶段地址分配的依据

    每个符号变量在目标代码生成时需要确定其在存储分配的位置(主要是相对位置)。语言程序中的符号变量由它被定义的存储类别(如在C、FORTRAN语言中)或被定义的位置(如分程序结构的位置)来确定。首先要确定其被分配的区域。

    例如,在C语言中首先要确定该符号变量是分配在公共区(extern)、文件静态区(extern static)、函数静态区(函数中static)、还是函数运行时的动态区(auto)等。其次是根据变量出现的次序,(一般来说)决定该变量在某个区中所处的具体位置,这通常使用在该区域中相对区头的相对位置确定。而有关区域的标志及相对位置都是作为该变量的语义信息被收集在该变量的符号表属性中。

    二、符号表的组织方式

    1. 符号表分为几大栏,主键是什么?

    • 名字栏: 也称主栏,关键字栏.
    • 信息栏: 记录相应的不同属性,分为若干子栏

    2. 什么是各项,各栏等长,说明利弊?

    各项各栏所占存储单元的长度固定,
    可以用固定分配空间,但是会有剩余空间未使用造成浪费

    什么是间接方式安排符号表的信息?

    如果各种名字所需的信息(INFORMATION )空间长短不一,那么,我们可把 一些共同属性直接登记在符号表的信息栏中,而把某些特殊属性登记在别的 地方,并在信息栏中附设一指示器,指向存放特殊属性的地方

    三、符号表的整理、查找

    1. 顺序表;

    使用一个一维数组或多个一维数组存放符号串名字和相关性息

    • 特点:
    1. 按名字出现的先后顺序依次填入;
    2. 查找时从头到尾逐个查找;
    • 缺点: 查找很慢
    • 改进的思路:
    1. 按照编程习惯可以反序查找。
    2. 按“最新最近”访问原则形成一条指向表的链子,每次查找时都按着这条链所指的顺序查找

    2. 对半查找与二叉树;

    • 在造表时把表格中的项按名字的“大小”顺序整理排列。
    • 所谓名字的“大小”通常是指名字的内码二进制。
    • 对于经顺序化的表格的查找可用对折法。
    a. 对折法的查找方法如下:
    1. 首先把要查找的项和中项(即第[n/2]+1项)作比
      较,若相等,则宣布查找成功。
    2. 若要查找的项小于中项,则继续在1〜[n/2]的各项
      中去查找。
    3. 若要查找的项大于中项,则就到[n/2]+2〜n的各项
      中去查找。

    平均查找次数1+log2n

    b. 杂凑技术。
    1. 假定有一个足够大的区域,这个区域用来填写一张含N项的符号表。构造一个地址函数H,对任何名字,H函数的取值在0至N-1之间。即不论对此项查表或填表,都能从H函数中获得它在表中的位置。
    2. 对地址函数H有两点要求:
      1. 函数的计算要简单、高效;
      2. 函数值能比较均匀的分布在0至N-1之间。
    3. 构造函数H的办法:
    • 直接地址法、数字分析法、
    • 平方取中法、折叠法、除留余数法、随机数法
    1. 解决地址冲突的办法:
    • 开放地址法、再哈希法、
    • 链地址法、建立一个公共溢出区
    • 杂凑技术:使用一张杂凑链表通过间接方式查填符号表。

    将具有相同杂凑值符号名连成一串,便于线性查找。杂凑表是一个可容纳N个指示器值的一维数组,它的每个元素的初值全为null。符号表除了通常包含的栏外,还增设了一链接栏,他把所有持有相同杂凑值的符号名连接成一条链。

    四、名字的作用范围

    1. 什么是最近嵌套作用域原则,具体描述Pascal语言的名字是否满足最近嵌套作用域原则?

    • 最近嵌套作用域规则:
      即对每个过程指定一个唯一的编号,以便跟踪过程里的局部名字。在符号表中,表示局部名字用一个二元组: < 名字,过程编号 >对一个名字查找符号表是:只有当表项中的名字其字符逐个匹配,并且该记录相关的编号和当前所处理的过程的编号匹配时,才能确定查找成功.

    2. 嵌套结构型程序设计语言的符号表:

    1. 针对符号表设计为栈符号表,新名字出现总是从栈顶填入。为了保证从内层向外层查,查找 操作从符号表的栈顶往底部查找。TOP指向栈顶第一个可用单元,P总是指向最新子符号表首地址。
    2. 过程的嵌套层次表(display),是引入的一个显示层次关系表。其作用是为了描述过程的嵌套层次,指出当前正在活动着的各嵌套的过程(或 函数)相应的子符号表在栈符号表中的起始位置(相对地址)。
      display表本身也是一个栈,每进入一个新的过程(或函数),栈顶指针增1;每退出一个新的过程(或函数),栈顶指针减1。栈顶指针总是指向当前正在处理的最内层的过程在栈符号表中 的起始位置。
    3. 在信息栏中引入一个指针域(previous),用来链接它在同一个过程内的下一名字在表中的下标(相对位置)。每一层最后一个域名字,指针域之值为0。这样每当需要查找个新名字时,就能通过display表找出当前正在处理的最内层的过程及所有外层的子符号表在栈符号表中的位置。然后通过指针域可以找到同一个过程内的所有被说明的名字。

    五、符号表的内容

    (1)类型(整、实、双实、字符、指针等);
    (2)种属(简单变量、数组或结构体等);
    (3)长度(所需的存储单元数);
    (4)相对数(存储单元相对地址);

    展开全文
  • 编译原理——符号表习题

    千次阅读 2020-06-22 22:55:33
    编译过程中,每当识别出一个新名字,就将它加入符号表中,并且在词法分析和语法语义分析阶段会陆续填入该名字的有关信息,这些信息在编译的不同阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查和产生...

    1.符号表有什么作用?

    编译过程中,每当识别出一个新名字,就将它加入符号表中,并且在词法分析和语法语义分析阶段会陆续填入该名字的有关信息,这些信息在编译的不同阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码。在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据。

    2.符号表有哪些构造和查找方式?

    构造和查找符号表的方式主要有:线性查找、二叉树和杂凑技术。

    线性查找按照关键字出现的顺序填写各个项,查找时也按照顺序逐个查找。为了提高查找效率,可以按照“最新最近”访问原则将符号表构建为自适应线性表。

    基于二叉树的查找方式,将符号表构造成一颗二叉排序树,当待查名字和树根所记录的名字相等时,查找成功;如果待查名字小于树根记录的名字,则继续在左子树上查找;否则继续在右子树上查找。

    杂凑技术将符号表中的名字按照杂凑函数计算所得的值组织成杂凑表,查找时用杂凑函数计算待查名字的值,在杂凑表相应的位置查找名字的记录。

    3.

    PROGRAM P(input, output)
         const norw=13;
         var    l,k:integer;
                  word:ARRAY[1..norw]of char;
         procedure  getsym;
         var i,j:integer;
                procedure  getch(word:real);
                      begin
                       ...
                      end;{getch}
         begin
                ...
                i:=1;k:=i+j;
                ...
         end;{getsym}
         procedure block(lev,lx:integer);
              var dx,txo:integer;
              procedure enter(k:real);
                   begin
                       ...
                   end;{enter}
              procedure stat(fs:integer);
                   var i,cxl:integer;
                   procedure ex(fs:integer);
                         var  addop:real;
                         procedure term(fs:intger);
                              var  i:integer;
                                  begin
                                       ...
                                       j:=cxl;
                                       ...
                                  end;{term}
                          begin   ...end;{ex}
                   begin ...  end;{stat}
                begin ...  end;{block}
          begin ...  end;{P}
    

    1)画出扫描到getsym过程体之前的栈符号表,并要求指明DISPLAY和TOP值。(20分)

    2)画出扫描到term过程体之前的栈符号表,并要求指明DISPLAY和TOP值。 (20分)

    3)编译term的过程体时,试以该过程体中出现的变量i、cxl为例说明其查找范围的控制步骤。(10分)

    解:1)
    在这里插入图片描述
    2)
    在这里插入图片描述
    3)取得DISPLAY栈顶20,查过程term栈顶子符号表,顺Previous链,在21号单元找到i,未找到cxl;取得DISPLAY次栈顶17,查过程ex子符号表,未找到cxl;取得DISPLAY次次栈顶13,查过程stat子符号表,找到cxl。

    展开全文
  • 符号表 -(编译原理)

    万次阅读 2019-03-12 16:15:11
    符号表编译程序工作的过程中需要不断收集、记录和使用源程序中一些语法符号的类型和特征等相关信息。这些信息一般以表格形式存储于系统中。如常数表、变量名表、数组名表、过程名表、标号表等等,统称为符号表。...
  • 从之前的编译过程的演示可以看出,各个编译单元之间是互相不可知的。   目标文件:由编译所生成的文件,以机器码的形式包含了编译单元里所有的代码和数据,以及一些其他的信息。     下面我们具体看看...
  • 编译原理系列之八 符号表

    千次阅读 2018-12-25 14:32:00
    符号表 符号表用来体现作用域与可见性信息 符号表的作用: ① 收集符号属性;(词法分析) ② 上下文语义的合法性检查的依据;(语法分析) ③ 作为目标代码生成阶段地址分配的依据;(语义分析) 符号表中...
  • 编译原理 第八章复习题 符号表

    千次阅读 2019-06-19 10:21:48
    编译处理过程中,符号表只有在词法分析阶段和语法分析阶段才是有用。正确 符号表常用的操作不包括(B)。 A 查询给定的名字是否已在表中 B 检查名字是否符合标识符命名的文法 C 对给定名字,往表中填写或更新它的...
  • 编译原理第八章-符号表

    千次阅读 2019-06-13 22:44:38
    2 在编译处理过程中,符号表只有在词法分析阶段和语法分析阶段才是有用。 正确 3 符号表常用的操作不包括(B )。 A 查询给定的名字是否已在表中 B 检查名字是否符合标识符命名的文法 C 对给定名字,往表中...
  • lmdb编译过程中出现无法解析的外部符号 NtCreateSection、NtClose、NtMapViewOfSection
  • 编译原理实验 之 符号表

    千次阅读 2012-05-29 00:22:40
     编译器首先都是进行的词法分析,而词法分析的基础就是符号表.  把不同类型的数据和其操作,属性都先预留下来,后面词法分析的时候可以用到.  这次的实验只是很简单的模拟而已,所以代码比较简单,编译实验...
  • 编译原理结构框架8符号表管理

    千次阅读 2014-12-03 21:19:06
    第8章 符号表管理 重点:符号表的作用,符号表的组织结构,符号表与作用域。 难点:符号表的组织结构及其性能评价。 8.1符号表的作用 n符号表是以名字为关键字来记录其信息的数据结构,其上支持的两个最基本操作...
  • 编译过程中如果出现未定义标识对于某些机型,如Undefined symbols for architecture arm64: "_OBJC_CLASS_$_CIImage", referenced from:你先检查下是否导入了对应的库文件,其次在看下是否导入了对应的头文件, ...
  • Ant进行javac编译时,如果出现找不到符号的错误,则可能是缺少类路径,则需要配置依赖的jar包,使用Ant&lt;classpath&gt;的标签,加载依赖的jar包。 如图所示: 但是我遇到的问题是配置的classpath是正确...
  • Java编译过程、c/c++编译过程区别

    千次阅读 2016-11-17 14:45:33
    Java编译原理 1、Java编译过程与c/c++编译过程不同 ...因此在编译过程中,编译程序通过查表将所有对符号的引用转换为特定的内存偏移量,以保证程序运行。 Java编译过程: Java编译器却不将对变量和方法的
  • C语言编译过程

    千次阅读 2018-04-01 12:14:06
    C语言编译过程为:预处理--&gt;编译--&gt;汇编--&gt;链接1、预处理(Pre-processing) 对源程序中的伪指令(以#开头的指令)和特殊符号进行处理。伪指令包括:宏定义指令、条件编译指令、头文件包含...
  • 编译过程和ELF文件

    千次阅读 2019-04-19 15:59:05
    一、C/C++编译过程 二、ELF文件 三、符号解析和重定位 四、静态链接和动态链接 一、C/C++编译过程 C/C++程序从源代码到可执行文件需要经理预处理(预编译),编译,汇编,链接四个过程: 1、预处理:对源代码...
  • 编译原理手记03-词法分析与符号表

    千次阅读 2014-07-21 19:35:28
    符号表 。 比如词法分析器解析出了一个"if",之后词法分析器就会挨个儿表的查找这个"if",这时它在关键字表中找到了这个"if",然后就会得出这样的结果:if是一个关键字! 同样的道理,如果解析出的一个...
  • java编译过程

    千次阅读 2018-06-10 21:29:06
    摘自:深入理解JVM虚拟机A、解析与...而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符都可以成为标记语法分析:根据Token序列构造抽象语法树的过程2、填充符号表完成语法分析和词法分析后,就是填...
  • 编译过程

    千次阅读 2013-01-24 14:50:19
    1、利用编译程序从源语言编写的源程序产生目标程序的过程。  2、用编译程序产生目标程序的动作。  编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。...
  • Android ELF文件编译符号隐藏

    千次阅读 2017-02-21 14:59:49
    今天来介绍Android下面NDK编译so时,怎样实现符号隐藏,gcc默认的情况下是将所有符号都导出的,为了安全启见可以将符号隐藏,可以给破解带来很大的麻烦,另外还可以有效减小so文件的大小,还有一个好处是可以加快...
  • 编译过程概述: 编译程序完成从源程序到目标程序的翻译工作,是一个复杂的整体的过程。从概念上来讲,一个编译程序的整个工作过程是划分成阶段进行的,每个阶段将源程序的一种表示形式转换成另一种表示形式,各个...
  • Javac编译过程

    千次阅读 2016-03-04 17:50:02
    Javac编译过程大致分为4个过程,分别是: 词法分析 语法分析 语义分析 代码生成 词法分析   词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的...
  • c++编译过程

    千次阅读 2018-03-03 14:58:48
    c++编译过程简介了解编译过程的益处c++工程相关的问题什么是库?静态库和动态库又有什么区别?头文件起什么作用?编译过程简介名词:编译:把源文件中的源代码翻译成机器语言,保存到目标文件中。如果编译通过,就会...
  • javac编译过程

    千次阅读 2018-08-15 15:39:15
     JVM规范定义了Class文件结构格式,但没有定义如何从java程序文件转化为Class文件,所以不同编译器可以有不同实现。  从javac编译器源码来看,其编译过程... 1、解析与填充符号表过程:解析主要包括词法分析...
  • C++的编译过程及原理

    万次阅读 多人点赞 2018-09-27 20:25:03
    目录 文章目录目录C和C++的编译 本次内容是关于编译过程的,内容如下: C和C++的编译 C和C++的编译

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 269,379
精华内容 107,751
关键字:

编译过程符号表