精华内容
下载资源
问答
  • 编译原理之符号表

    千次阅读 2019-06-20 12:24:56
    作为目标代码生成阶段地址分配的依据二、符号表的组织方式1. 符号表分为几大栏,主键是什么?2. 什么是各项,各栏等长,说明利弊?什么是间接方式安排符号表的信息?三、符号表的整理、查找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)相对数(存储单元相对地址);

    展开全文
  • 用g++编译程序符号说明

    千次阅读 2017-06-15 10:44:30
    用g++编译程序时,-l 与-L各是什么意思?还有-I 连接mysql数据库的程序,要用g++ tt.cc -lmysqlclient -L /usr/lib/mysql -I/usr/include/mysql才能编译 g++ tt.cc -lmysqlclient -L /usr/lib/mysql -...

    用g++编译程序时,-l 与-L各是什么意思?还有-I

    连接mysql数据库的程序,要用g++ tt.cc -lmysqlclient -L /usr/lib/mysql -I/usr/include/mysql才能编译

    g++ tt.cc -lmysqlclient -L /usr/lib/mysql -I/usr/include/mysql

    -l 连接共享库 mysqlclient 吧
    -L lib 位置
    -I #include <xxxx.h>; xxxx.h 位置

    一、编译过程: 
    1)预处理,生成.i文件 
    2)转换成为汇编语言,生成.s文件 
    3)汇编变为目标代码(机器代码),生成.o文件 
    4)链接目标代码,生成可执行程序。  
    
    二、常用编译选项 
    tips:选项必须独立给出:‘-pg’和‘-p -g’完全不同  
    
    -c:编译或汇编源文件,不做连接。 
    G++ -c test.cpp输出test.o  
    
    -o file:制定输出文件为file  
    
    -Wall: 输出所有编译警告(最好加上)  
    
    -Dmacro=XXX:定义宏。  
    
    -shared:生成一个共享库文件 
    g++ -shared -o libtest.so test.o  
    
    -fPIC:生成位置无关目标代码,适用于动态连接。  
    
    -llibrarytest:连接名字为librarytest的库 
    真正名字是liblibrarytest.so(a) so是动态库,a是静态库。 
    严格按照文件名搜索,版本号要创建软连接。 
    编译搜索目录:
    用户-L指定, LIBRARY_PATH,系统目录/lib  /usr/lib 
    运行搜索目录:
    LD_LIBRARY_PATH,ld.so.cache & /etc/ld.so.conf ,系统目录  /lib  /usr/lib 
    动态库和静态库同名,优先动态库。  
    
    -Ldir:添加库文件搜索路径 -Idir(include):添加头文件搜索路径  
    
    -g:产生调试信息  
    
    -olevel:优化级别,一般用o2  
    
    三、静态库、共享库 
    静态库:一些.o文件打包,在被连接后成为程序的一部分。 
    编译方法 
    -g++ -c test.cpp 
    -ar res libtest.a test.o 
    链接方法: 
    -g++ -Wall -o test testMain.cpp -ltest -L./  
    
    共享库:链接的时候不会被复制到程序中。 
    编译方法: 
    g++ -c fPIC test.cpp	
    //要动态 g++ -shared -WI, -soname, libtest.so, -o libtest.so.1.0.1 test.o 
    mv libtest.so.1.0.1 /usr/lib 
    sudo ldconfig & || ll /user/lib/libtest.so //创建一个软连接。     
    链接方法:
    g++ -o test test.cpp ./libtest.so -ldx_cxx  
    
    四、常用命令 
    ldd:显示程序依赖的同台共享库。  
    file:查看文件格式信息。  
    ldconfig:在搜寻目录下搜索出可以共享的动态链接库 
    参考:
    http://www.cnblogs.com/forward/archive/2012/01/10/2318483.html
    展开全文
  • 符号表&词法分析 符号表及其作用 二符号表的内容 存储分配&符号表的组织 四标识符的处理 五符号表的结构和存取 六词法分析 七取单词 八读标识符 九词法分析器的自动生成 十一个实例 符号表及其作用 1编译程序逻辑图 ...
  • 编译过程和符号表重定位问题:转载至:点击打开链接 对于代码的编译问题千头万绪从何说起呢,首先来说一下计算机是如何处理应用程序的,实质上应用程序是通过操作系统来应用机器指令操控硬件设施完成各种任务的,...

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

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

    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中地址,具体是如何做到延迟绑定的,推荐看《程序员的自我修养》一书。

    小结

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

    展开全文
  • 词法分析程序:读字符流的源程序、识别单词 语法分析程序:层次分析,把源程序的单词序列组成语法短语(表示成语法树). 语义分析程序:语义审查(静态语义) ...目标代码生成程序 :转换为机器指令上的绝对指...
    1. 词法分析程序:读字符流的源程序、识别单词
    2. 语法分析程序:层次分析,把源程序的单词序列组成语法短语(表示成语法树).
    3. 语义分析程序:语义审查(静态语义)
      上下文相关性
      类型匹配
      类型转换
    4. 中间代码生成程序:源程序的内部(中间)表示
      三元式、四元式、P-Code、C-Code、 U-Code、bytecode
    5. 代码优化程序: 优化中间代码,节省时间、空间
    6. 目标代码生成程序 :转换为机器指令上的绝对指令代码
    7. 符号表管理程序:记录源程序中使用的名字
      收集每个名字的各种属性信息
      类型、作用域、分配存储信息
      这几种程序的主要任务可以用一张图表示如下:
      在这里插入图片描述
      其中没写出来的程序任务:
    • 符号表管理:记录源程序中使用的名字。收集每个名字的各种属性信息(类型、作用域、分配存储信息)。
    • 出错管理:检查错误、报告出错信息、排错、恢复编译工作。
      在这里插入图片描述
    展开全文
  • 习题 第9章 符号表 第10章 目标程序运行时的存储组织 一 课本练习部分 9.2 9.3 10.6 10.8 2.2(第2章) 10.10
  • 编译原理 第八章复习题 符号表

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

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

    千次阅读 2017-05-19 16:04:45
    编译程序也叫编译系统,是把用高级语言编写的面向过程的源程序翻译成目标程序的语言处理程序。 编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;中间代码生成;代码优化;目标代码...
  • 程序的连接之符号符号表

    千次阅读 2020-02-24 17:03:17
    程序的链接,链接的第一步就是符号的解析。要理解符号的解析过程,得先搞明白以下几点: 什么是符号 什么是符号的定义,什么是符号的引用 链接符号的类型有哪些,各自有什么特点 符号表的结构 链接器对符号...
  • C/C++编译和链接过程详解 (重定向,导出符号表,未解决符号表) 分类: C++2012-03-16 13:06 1255人阅读 评论(3) 收藏 举报 编译器ideredirect语言tableclass 详解link  有 些人写C/C++(以下...
  • 编译程序

    2013-04-19 11:23:13
    编辑——在开发工具里写程序预处理——加载一些头文件、宏替换编译——将.c 或者 .cpp文件转化为目标文件(.o)链接——将所有的.o及一些动态链接库文件(.exe .dll等)整合到一块...编译程序把一个源程序翻译成目标程序
  • 符号表的作用和地位

    千次阅读 2018-07-11 13:14:52
    转自:https://blog.csdn.net/aigoogle/article/details/45076617在编译程序中符号表用来存放语言程序出现的有关标识符的属性信息,这些信息集中反映了标识符的语义特征属性。在词法分析及语法在分析过程不断...
  • 在编译过程编译程序用来记录源程序各种名字的特性信息 也称为名字特性。 名字 程序名、过程名、函数名、用户定义类型名、变量名、常量名、枚举值名、标号名等。 特性信息 上述名字的种类、类型、维数、...
  • 语义处理: 声明语句(登录名字的特征信息到符号表上), 执行语句(按某种操作的目标结构生成代码) 非分程序结构: 主程序与子程序, 函数分开 分程序结构: 主程序与子程序, 函数嵌套 作用域分 静态作用域(c, c++) 和 ...
  • 编译程序工作过程

    千次阅读 2018-04-26 15:21:22
    编译程序也叫编译系统,是把用高级语言编写的面向过程的源程序翻译成目标程序的语言处理程序。编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;中间代码生成;代码优化;目标代码...
  • 符号表的作用

    千次阅读 2020-03-31 20:57:31
    编译程序中符号表用来存放语言程序出现的有关标识符的属性信息,这些信息集中反映了标识符的语义特征属性。在词法分析及语法在分析过程不断积累和更新表中的信息,并在词法分析到代码生成的各阶段,按各自的...
  • 符号表之一:符号属性

    千次阅读 2017-12-10 23:59:06
    符号表贯穿词法分析到语义处理的全过程,是用来存放在程序中出现的符号标识符(变量、函数、过程等)的语义属性在词法分析及语法分析阶段不断积累和更新符号表中的信息,并在词法分析到代码生成甚至动态链接的各个...
  • 以下是在论坛看到的两种解释:(1)如果是要你的代码在编译时发现编译器类型,就判断_cplusplus或_STDC_宏,通常许多编译器还有其他编译标志宏,#ifdef __cplusplus cout&lt;&lt;"c++";#else ...
  • 编译程序完成从源程序到目标程序的翻译工作,是一个复杂的整体的过程。从概念上来讲,一个编译程序的整个工作过程是划分成阶段进行的,每个阶段将源程序的一种表示形式转换成另一种表示形式,各个阶段进行的操作在...
  • 编译程序基本原理

    千次阅读 2018-06-25 21:40:01
    编译程序和解释程序 人们利用高级语言与计算机进行交互, 但计算机仍然只能理解和执行由 0, 1序列构成的机器语言, 因此高级程序设计语言需要翻译, 担负这一任务的程序称为"语言处理程序", 由于应用的不同, 语言之间...
  • 符号表

    2008-12-12 10:16:00
    编译程序时放在符号表里的不光是内存地址和函数/变量的对应关系,还有很多在编译时要用到的信息。比如该节点的各种属性(类型,作用域,分配空间...这种符号表中主要包含函数/变量的名称和地址对应关系,其中的地址一般
  • 以下是在论坛看到的两种解释: (1)如果是要你的代码在编译时发现编译器类型,就判断_cplusplus或_...如果要判断已经编译的代码的编译类型,就用nm查一下输出函数符号是否和函数名相同。(相同为c,不同为c++。详
  • 如果编译器在编译cpp文件,那么 _cplusplus就会被定义,如果是一个C文件被编译,那么 _STDC_就会被定义,_STDC_是预定义宏,当它被定义后,编译器将按照ANSIC标准来编译C语言程序。 所以,可以采用下列程序示例判断...
  • 1.预处理标识符又称预定义符号,它们的值是(字符串常量、十进制数字常量) (1)__FILE__ :表示进行编译的源文件名 (2)__LINE__ :表示文件当前行的行号 (3)__DATE__ :表示文件被编译的日期 (4)__TIME_...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 204,643
精华内容 81,857
关键字:

编译程序中符号表的主要目的是