精华内容
下载资源
问答
  • 2020-12-20 16:38:36

    3、符号表的内容:
    3.1、符号表存储的内容:

    • 目标文件中引用的全局变量以及函数
    • 目标文件中定义的全局变量以及函数

    3.2、本质上符号表表达的内容:

    • 我能提供给其它文件使用的符号
    • 我需要其它文件提供给我使用的符号

    编译器在编译过程中每次遇到一个全局变量或者函数名都会在符号表中添加一项,最终编译器会统计出一张符号表

    4、符号决议:
    在符号决议阶段,链接器需要做的工作就是确保所有目标文件中的符号引用都有唯一的定义
    链接器会依次扫描每一个给定的目标文件,同时维护了两个集合,一个是已定义符号集合D,另一个是未定义符合集合U,扫描完所有目标文件,U不为空则说明有未定义。

    总结:

    1、编译器对每个cpp文件编译生成该文件拥有和需要的符号构成符号表;
    2、链接器对多个目标文件形成发布件时,判断所有目标文件中的符号引用都有唯一的定义(检查符号未定义问题)。

    参考资料:彻底理解链接器:二,符号决议

    更多相关内容
  • 编译原理——符号表

    2021-06-01 08:08:42
    符号表的组织与查找 间接的方式存储是名字存储的是指针 为了合理的使用空间,大部分情况是用的分成M个子的方式 符号表的整理和查找 符号表的内容 tx 指向符号表下一个插入的位置 name 名字标识符...

    符号表的组织与查找

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    间接的方式存储是名字中存储的是指针
    在这里插入图片描述
    在这里插入图片描述
    为了合理的使用空间,大部分情况是用的分成M个子表的方式

    符号表的整理和查找

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    符号表的内容

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    tx 指向符号表下一个插入的位置
    name 名字标识符 (是主栏目,查找的时候都是根据名字来索引)
    kind 名字种类,可以是常量,变量,类型过程
    lev 名字所在程序体的静态层次,规定主程序的层次为1,主程序中定义层次为2,以此类推
    typ 名字类型,整形,字符型,布尔型,数组,对于没有类型的名字填入notype
    normal 是布尔量,表示变量是否为变量形参名,当名字是变量形参填入false其他情况填入true或者不填(因为变量形参需要传地址,所以用这个栏目进行标记)
    ref 当名字为数组变量或者数组变量名时,ref指向该数组在数组信息表中的位置
    当名字为过程名时,ref指向该过程在程序体表中的位置;
    其他情况ref为0

    adr/val/size: 这三个使用同一个存储空间,不同名字选用不同参数在这里插入图片描述

    link:
    在这里插入图片描述

    在这里插入图片描述
    bx指向他下一个空闲的位置

    lastpar:
    是个指针
    在这里插入图片描述
    last:
    在这里插入图片描述
    psize:
    在这里插入图片描述
    vsize:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    ax指向表格下一个填入位置
    多维数组的存储是把他看做是一维数组,每一维度用一行进行存储
    在这里插入图片描述
    在这里插入图片描述

    符号表的使用

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 语义分析阶段要构建一个符号表为整个后序过程提供支持 一、抽象地址 1.1 地址分配 进行编译的时候为每一个变量标识符都分配一个相对地址,当目标程序真正运行的时候,一旦首地址确定下来,那么所有的变.

    一个标识符的语义信息含有三部分内容,种类 类型 抽象地址
    种类可以分为:常量标识符、类型标识符、变量标识符、域名标识符、过函标识符
    类型可以有:基本数据类型、子界、数组、枚举、结构体、集合、文件、指针等等

    语义分析:

    1. 静态分析——静态语义
    2. 动态分析——动态语义

    核心:

    1. 程序中是否有语义错误
    2. 在语义分析阶段要构建一个符号表为整个后序过程提供支持

    一、抽象地址

    1.1 地址分配

    在这里插入图片描述

    • 在进行编译的时候为每一个变量标识符都分配一个相对地址,当目标程序在真正运行的时候,一旦首地址确定下来,那么所有的变量对应的地址就可以确定下来了,这是一种比较简单直观的方法,例如fortune语言就是用这样的方式,但是也有其局限性。比如有动态申请地址空间的时候,这种方法不适用。

    • 两个比较典型的例子:

      1. 指针变量,他是在程序的运行过程中可以动态的申请存储空间,申请空间的次数和占有多大空间,这个在程序没有执行的时候是没有办法确定的,如果存在于程序的某一个分支或者循环中,程序没运行则没有办法确定。既然没有办法确定,就没有办法预先分配存储空间。
      2. 程序中的递归函数,这是一个比较有特点的调用形式。每次调用一个函数都要给它的形参和内部的变量开辟对应的存储空间,在函数被递归调用(最外面那个函数被调用)的时候,一个局部变量比方说x的时候他对应的就不是一个空间,而是一串的存储空间,这一个变量占用多少空间取决于一个递归程序执行的次数,静态的时候没有办法确定次数,因为影响次数的因素非常多,比方说跟输入有关、跟什么条件有关,所以没有办法静态的确定到底占多大的空间,这样的情况也不能采用静态的方式来分配。
    • 只能用动态的方式,给变量在程序中分配的不是一个(程序中变量分配的地址)不是一个具体的地址,而是一个抽象地址。一个变量对应的存储单元现在是不确定的只是一个抽象的地址,在真正的目标程序运行的时候,再把这个地址映射到一个实际的物理地址上,这样的话,需要多少,就可以动态的分配多少,通过抽象地址进行映射,得到所需要的存储空间。现在大多数语言,因为有动态的数据结构,有递归,所以都采用的是动态的存储空间形式,动态的管理模式。

    • 这是两种最常用的地址分配方式。现在主要采用动态的方式

    1.2 抽象地址结构

    在这里插入图片描述

    • 采用的都是动态的管理模式,一个变量分配的都是一个抽象地址。抽象地址通常来说具有两部分组成,第一部分为层数,第二部分为偏移。
    • 程序设计语言从函数定义结构上可以分成两大类,一类属于并列式语言,一类属于嵌套式语言。并列就是c fortune,函数之间都是并列的,一个函数内部不可以定义其他的函数。而另外一类语言就是属于嵌套式语言,比如pascal语言,属于嵌套结构,在一个函数中可以定义另外一个函数,还可以继续定义。这种嵌套的方式,可以直接使函数的使用范围受到约束,避免了不应该调用这个函数的调用出现,避免程序中出现不应该有的问题。
    • 这是两类常见的程序设计语言,由于编译是针对一般程序设计语言来进行考虑的,课程是针对一般性的程序设计语言来介绍编译过程的原理,所以可能对不同类型的语言都要给出介绍

    1.3 层数定义

    在这里插入图片描述
    对于并列式语言这个层数是不是没有意义呢? 其实也不是,只是这个层数相对更为简单。比如把全局的变量定义成0层,函数中的变量定义成1层。

    1.4 过程活动记录

    在这里插入图片描述

    1. 偏移: 每次函数被调用的时候,都会给函数分配一片空间,包括一些管理信息,形参、局部变量、临时变量要占据存储单元,偏移量是针对于这个起始地址而言,他的偏移是多少,以后只需要知道起始位置在哪,就可以找到对应变量所在的单元。起始位置+偏移量就是对应的一个物理地址,可以通过这样的形式把它表示出来

    2. 过程活动记录:一个函数或者一个过程在他真正调用的时候会占用一片存储空间,我们把这片存储空间称作过程活动记录,表示一个函数被真正调用时,它所占用的存储空间,这个存储空间中包括的信息主要有: 管理信息返回地址、寄存器现场的信息等等的管理信息,这些信息占用的空间大小是固定的,对于一个具体的目标机而言,这个是固定的

    3. 形参区,在真正函数调用的时候也要有具体的存储单元,所以也要给他们分配具体的存储单元,比方说整型的形参分多少、数组的分多少。 局部变量区是指在函数内部声明的变量所占用的空间在这一块区域分配。

    4. 临时变量区,在生成目标代码之前,可能会产生很多的中间计算结果,中间的结果全都存在寄存器中恐怕是做不到的,因为有的时候结果比较多,寄存器的个数是有限的,所以不能全都存在寄存器里,有一部分要存在内存中,既然是存在内存里,就要给分配一块存储空间,这里就是存的这种临时变量。

      这就是一个函数在调用的过程中所要占用的空间,这里的存储顺序为什么是按照这个顺序排列的呢?这个是按照处理的顺序分配的,比方说管理区是固定的,是函数调用的时候大小固定, 然后先处理的是形参,从函数头开始处理,所以给形参分区,然后是局部变量,然后是临时变量。这是过程活动记录的大概的结构.

    1.5 分配原则

    在这里插入图片描述

    • 分配原则,一个简单变量、数组变量、记录变量,分配的大小肯定是不一样的。简单的我们知道是确定的,其他的就不一定,所以一定要给出一个确定的分配原则。因为不同的目标机分配的单元个数可能是不一样的,有的机器是16位,有的是8位,这样对于一个变量来说占用的空间大小可能就不太一样,这里为了简单起见,假定了基本变量类型占几个,这个都是假定的,考试的时候如果题目中说假定占两个也没问题,至于说具体多少就要根据实际情况来确定
    • 临时变量分配1个存储单元,临时变量一般是一个计算结果,结果都是一个简单值,所以就假定占用一个。
    • 局部变量分配的单元要按照类型的长度分,每一种类型都要确定它的大小,目的是什么呢?就是为了分配存储单元的时候有一个依据。所以局部变量大小是根据类型来决定的,这也是类型的一个作用。
    • 形参有两类——值引用的形参(要把实参的值传给形参,不管实参是什么类型的 值都是传过来的,所以值引用型形参也是按照类型的大小来分空间)、地址引用型的形参(接受的是实参的地址信息,因此统一的给它分配一个存储单元,前提是地址用一个单元能保存出来,不管实参是简单类型也好,结构类型也好,传递过来的时候,假如一个数组,不是把这100个元素都传过来,而是传递的这个实参的地址,然后通过间接寻址的方式 对它进行操作,所以形参给的是一个存储单元)对于过程函数形参,一个函数作为形参的时候,通常要分配两个存储单元,第一个存储单元意义非常明确,以后要传的实参函数的入口地址,第二个是要构造运行环境的地址

    二、符号表

    2.1 符号表结构和总体组织

    在这里插入图片描述

    • 下面就要看一下具体的符号表的建设和管理情况。从语法分析之后得到的是一个token序列,token序列中的标识符部分都变成了:标识符->符号表
    • 符号表的结构,符号表存的就是标识符的语义信息。符号表的信息就是由两个,标识符 语义字(种类 类型 抽象地址)这样的话一个符号表就是由这样一串标识符的语义信息组成的,类型信息可以和类型信息表关联。

    2.2 New token与符号表的关系

    在这里插入图片描述

    2.3 符号表查表技术

    在这里插入图片描述
    一般使用顺序查表法。

    2.4 标识符的作用域

    在这里插入图片描述
    函数,从函数名后面开始就进入了局部化区,函数结束退出这个局部化区。 另外一个是域名是一种特殊的局部化区,记录变量在进入记录之前,也是有一个局部化区,特殊的情况就是域名和变量名可以相同,但是由于有域名,就跟一般的变量进行了区分。 不同的语言在约定上不太相同,c语言中的分程序结构用{}的部分也是局部化区,在{}可以再声明一些变量和标识符,出了这个花括号就没了,

    2.5 局部化区的语义错误检查

    在这里插入图片描述
    在一个局部化区的时候,要考虑一些语义上的问题,主要考虑的是两类语义错误的问题,第一大类的语义错误就是变量的重复声明,第二大类就是变量没有声明的使用。
    第一类问题:变量的重复声明。 在一个程序的局部化区里,同一个标识符不能被声明两次
    第二类问题:标识符的使用没有声明,现在的程序设计语言都属于强类型语言,也就是说所有的标识符都要给出对应的声明,没有声明出现的标识符认为就是有错的。比方说(),在某些弱类型的语言中也可以不用这么声明,比方说qbasac。

    2.6 标识符处理原则

    在这里插入图片描述
    作废方法:

    1. 直接删除,直接将退出的局部化区的全部内容从符号表中删除,但是这对于后续维护等操作来说会带来困难
    2. 在退出的位置开辟一个新的空间存储一个指向当前局部化区最开始位置的一个指针,后面的变量在查找的时候从下往上找会略过这个区域,但是注意,返回的是局部化区的开始而不是完全跳出,因为这样还可以在当前位置再次跳入该局部化区。

    2.7 实例分析

    例1:

    在这里插入图片描述

    1. 符号表(忽略m定义)
    NameLevelOff
    P1NULL
    x2Off0
    a2Off0+2
    i2Off0+1002
    P12NULL
    a13Off0
    a23Off0+1
    x3Off0+1001
    a3Off0+1002
    P23NULL
    n4Off0
    x4Off0+1
    1. 根据函数中的使用性出现,从下往上进行使用性变量定义的寻找
      在这里插入图片描述

    例2:

    在这里插入图片描述
    答案在下一节笔记上。

    嵌套式语言并列式语言的比较

    在这里插入图片描述

    展开全文
  • 中间代码 ...也被称为逆波兰表示,操作数前,操作符紧随其后,无需用括号限制运算的优先级和结合性 表示并不惟一 x := first_token; while not end_of_exp loop if x in operands then push x; --

    静态语义分析

    语法制导翻译处理语义的基本方法

    • 以语法分析为基础,在语法分析得到语言结构的结果时,处理附着于此结构上的语义,如计算表达式的值、生成中间代码等

    语法与语义

    • 语法是指语言结构,即语言的“样子”;
    • 语义是附着于语言结构上的实际含义,即语言的“意义”

    语义分析的作用:

    • 检查是否结构正确的句子所表示的意思也合法

    • 执行规定的语义动作

    • 例如如:

      • 表达式求值
      • 符号表填写
      • 中间代码生成等

    方法:

    • 语法制导翻译

    语法制导翻译

    基本思想:

    • 将语言结构的语义以属性的形式赋予代表此结构的文法符号,而属性的计算语义规则的形式赋予由文法符号组成的产生式。
    • 在语法分析推导或规约的每一步骤中,通过语义规则实现对属性的计算,以达到对语义的处理

    具体方法:

    • 将文法符号所代表的语言结构的意思,用附着于该文法符号的属性表示
    • 语义规则规定产生式所代表的语言结构之间的关系(即属性之间的关系),即用语义规则实现属性计算

    语义规则

    两种形式:

    • 语法制导定义 (算法)
      • 抽象的属性运算表示的语义规则 (公式,做什么)
    • 翻译方案 (程序实现,方法不唯一)
      • 具体的属性运算表示的语义规则 (程序段,如何做)

    语义规则也被习惯上称为语义动作

    • 忽略实现细节,二者作用等价(设计与实现)

    属性

    对于产生式A→α,其中α是由文法符号X1X2…Xn组成的序列,它的语义规则可以表示为关于属性的函数:b := f(c1, c2, …, ck)

    语义规则中的属性存在下述性质与关系:

    • (1) 若b是A的属性,c1, c2, …, ck是α中文法符号的属性,或者A的其它属性,则称b是A的综合属性。
    • (2) 若b是α中某文法符号Xi的属性,c1, c2, …, ck是A的属性,或者是α中其它文法符号的属性,则称b是Xi的继承属性。
    • (3) 称(4.1)中属性b依赖于属性c1, c2, …, ck。
    • (4) 若语义规则的形式如 f(c1, c2, …, ck),则可将其想像为产生式左部文法符号A的一个虚拟属性。属性之间的依赖关系,在虚拟属性上依然存在。

    属性之间的计算构成了语义规则计算的先后次序被称为属性的依赖关系

    • 例如:E→E1+E2 E.val:=E1.val+E2.val,则表明:E的属性.val由E1和E2的相应属性计算而来,E的属性依赖于E1和E2的属性

    注释分析树

    • 属性附着在分析树对应文法符号上,形成注释分析树
    • 类似的,将属性附着在语法树对应文法符号上,形成语法分析树

    注释分析树直观地反映属性的性质和属性之间的关系,所以画树还要标属性

    • 对于S标nc,对于M标stat,对于E标tc和fc *

    继承属性:

    • 自上而下计算的,从前辈和兄弟的属性计算得到,即“自上而下,包括兄弟”

    综合属性:

    • 自下而上计算的,从子孙和自身的其他属性计算得到,即“自下而上,包括自身”

    LR分析翻译方案的设计

    • LR分析中的语法制导翻译实质上是对LR语法分析的扩充:

    扩充LR分析器的功能:

    • 当执行归约产生式的动作时,也执行产生式对应的语义动作。
    • 由于是归约时执行语义动作,因此*限制语义动作仅能放在产生式右部的最右边

    扩充分析栈:

    • 增加一个与分析栈并列的语义栈,用于存放分析栈中文法符号所对应的属性值

    递归下降分析翻译方案的设计

    • 在产生式右部任何位置都可以嵌入语义动作;(与LR分析只能在最右边进行比较)

      • 由于是根据 LR 分析拓展,所以对于继承属性采用回填的办法
      • 即:产生式右边要用到继承属性的符号,在其相邻的右边多出一个 M(M -> ε),就是通过这个 M 的语义进行回填
    • 在函数返回值、参数、变量等设计存储空间

    后缀式

    也被称为逆波兰表示,操作数在前,操作符紧随其后,无需用括号限制运算的优先级和结合性

    表示并不惟一

    x := first_token;
    while  not end_of_exp 
    loop    if   x in operands
            then push x;          -- 操作数进栈
            else pop(operands);  -- 算符,弹出操作数
                 push(evaluate);  -- 计算,并将结果进栈
            end  if;
         	next(x);
    end loop;
    

    后缀式并不局限于二元运算的表达式,可以推广到任何语句只要遵守操作数在前,操作符紧跟其后的原则即可

    三地址码

    形式接近机器指令,且具有便于优化的特征

    • 顾名思义,是由不超过三个地址组成的一个运算

    题目中的三地址码序列需要像这样:

    (1) if a < b goto (3)
    (2) goto (8)
    (3) if c < d goto(5)
    (4) goto (8)
    (5) t1:= a + c
    (6) x:=t1
    (7) goto -
    

    语法:

    • result := arg1 op arg2结果存放在result中的二元运算arg1 op arg2

    • result := op arg1结果存放在result中一元运算op arg1

    • op arg1一元运算op arg1

    • result := arg1直接拷贝

    三元式

    (i)(op, arg1, arg2)

    • 序号(i)是它们在三元式表中的位置

    • 序号的双重含义:既代表此三元式,又代表三元式存放的结果

    • 存放方式:数组结构,三元式在数组中的位置由下标决定

    • 弱点:给代码的优化带来困难
      因为代码优化常使用的方法是删除某些代码或移动某些代码位置,而一旦进行了代码的删除或移动,则表示某三元式的序号会发生变化,从而使得其他三元式中对原序号的引用无效

    语法制导翻译

    1. 属性 .code:三元式代码,指示标识符的存储单元或三元式表中的序号
    2. 属性 .name:标识符的名字
    3. 函数trip( op,arg1,arg2 ):生成一个三元式返回三元式的序号
    4. 函数 entry(id.name):返回标识符在符号表中的位置存储位置
    产生式:					语义规则:
    (1) A→id:=E		{A.code:=trip(:=,entry(id.name),E.code)}
    (2) E→E1+E2		{E.code:=trip(+,E1.code,E2.code)}
    (3) E→E1*E2 	{E.code:=trip(*,E1.code,E2.code)}
    (4) E→(E1) 	 	{E.code:=E1.code}
    (5) E→-E1		{E.code:=trip(@,E1.code, )}
    (6) E→id		{E.code:=entry(id.name)}
    

    四元式

    1. 四元式与三元式的唯一区别是将由序号所表示的运算结果改为了由临时变量来表示

    2. 此改变使得四元式具有了运算结果四元式在四元式序列中的位置无关的特点,它为代码的优化提供了极大方便,因为这样可以删除或移动四元式而不会影响运算结果【避免了三元式的值与三元式在三元式组中的位置相关的弱点】

    3. 三地址码与四元式形式的一致性

      四元式 (op,arg1,arg2,result) ==> 三地址码 result := arg1 op arg2

      result的表示方法通常是给出一个临时名字,用它来存放运算的结果,被称为临时变量(语法制导翻译时可以随意引入临时变量,若干临时变量可以共用同一个存储空间)

    语法制导翻译

    1. 属性.code: 表示存放运算结果的变量
    2. 函数newtemp:返回一个新的临时变量,如T1,T2,…等
    3. 过程emit( op,arg1,arg2, result):生成一个四元式,若为一元运算,则arg2可空
    产生式:					语义规则:
    1)A→id:=E	{A.code:=newtemp; emit(:=, entry(id.name), E.code, A.code)}
    (2)E→E1+E2	{E.code:=newtemp; emit(+,E1.code,E2.code,E.code)}
    (3)E→E1*E2	{E.code:=newtemp; emit(*,E1.code,E2.code,E.code)}
    (4)E→(E1)	{E.code:=E1.code}
    (5)E→-E1	{E.code:=newtemp; emit(@,E1.code, , E.code)}
    (6)E→id		{E.code:=entry(id.name)}
    

    图形表示

    树作为中间代码,语法树真实反映句子结构,对语法树稍加修改(加入语义信息),即可以作为中间代码的一种形式(注释语法树)

    树语法制导翻译

    1. 属性.nptr:指向树节点的指针
    2. 函数mknode(op,nptr1,nptr2): 生成一个根或内部节点,节点数据是op, nptr1和nptr2分别指向的左右孩子的子树。若仅有一个孩子,则nptr2为空
    3. 函数mkleaf(node): 生成一个叶子节点
    产生式:					语义规则:
    (1) A → id := E 	{A.nptr:= mknode(:=,mkleaf(entry(id.name)),E.nptr)}
    (2) E → E1 + E2		{E.nptr:=mknode(+,E1.nptr,E2.nptr)}
    (3) E → E1 * E2 	{E.nptr:=mknode(*,E1.nptr,E2.nptr)}
    (4) E → ( E1 )  	{E.nptr:=E1.nptr}
    (5) E → - E1		{E.nptr:=mknode(@,E1.nptr, )}
    (6) E → id			{E.nptr:=mkleaf(entry((id.name))}
    

    树的优化表示DAG

    如果树上若干个节点有完全相同的孩子,则这些节点可以指向同一个孩子,形成一个有向无环图(Directed Acyclic Graph, DAG)

    • DAG与树的唯一区别是多个父亲可以共享同一个孩子,从而达到资源(运算、代码等)共享的目的
    • 仅需要在mknode和mkleaf中增加相应的查询功能
    • 首先查看所要构造的节点是否已经存在,若存在则无需构造新的节点,直接返回指向已存在节点的指针即可

    树与其他中间代码的关系

    1. 树表示的中间代码与后缀式和三地址码之间有内在联系
    2. 对树进行深度优先后序遍历,得到的线性序列就是后缀式,或者说后缀式是树的一个线性化序列
    3. 树的每个内部节点和它的孩子对应一个三元式或四元式

    符号表

    符号表的作用:连接声明引用的桥梁,记住每个符号的相关信息,如作用域和绑定等,帮助编译的各个阶段正确有效地工作

    符号表的空间存储应该是可以动态扩充的

    符号表设计的基本要求:目标是合理存放信息快速准确查找

    • 正确存储各类信息
    • 适应不同阶段的需求
    • 便于有效地进行查找、插入、删除和修改等操作;
    • 空间可以动态扩充

    逻辑上讲:

    • 每个声明的名字在符号表中占据一栏,称为一个条目,用于存放名字的相关信息

    符号表中的内容:

    • 保留字、标识符、特殊符号(包括算符、分隔符等) 等等
    • 多个子表:不同类别的符号可以存放在不同的子表中,如变量名表、过程名表、保留字表等
    • 存放方式:关键字+属性

    组合关键字至少应该包括三项:名字+作用域+类型

    构成名字的字符串的存储:

    • 定长数据/直接存放
      • 名字:直接存储名字
    • 变长数据(名字长度变化范围很大)/间接存放
      • 名字,起始地址;名字间可以用特殊符号隔开,也可以在名字中添加长度

    名字的作用域

    <1> 静态作用域规则(static-scope rule):

    • 编译时就可以确定名字的作用域,也可以说,仅从静态读程序就可确定名字的作用域。

    <2> 最近嵌套规则(most closely nested):

    • 以程序块为例,也适用于过程
    1. 程序块B中声明的作用域包括B;
    2. 如果名字x不在B中声明,那么B中x的出现是在外围程序块B’的x声明的作用域中,使得
      • B’有x的声明,并且
      • B’比其它任何含x声明的程序块更接近被嵌套的B

    线性表

    线性表应是一个(后进先出),以正确反映名字的作用域,即符号的加入和删除,均在线性表的一端进行

    查找: 从表头(栈顶)开始,遇到的第一个名字;

    插入: 先查找,再插入在表头;

    删除:

    • (a) 暂时:将在同一作用域的名字同时摘走,适当保存
    • (b) 永久:将在同一作用域的名字同时摘走,不再保存

    修改: 与查找类似,修改第一个遇到的名字的信息;修改可以用删除+插入代替

    效率(n个条目):

    • 一个名字的查找

      • 成功查找(平均):(n+1)/2
      • 不成功查找:n+1
    • 建立n个条目的符号表(最坏): ∑ i = 1 n i \displaystyle\sum^n_{i=1}i i=1ni = (n+1)(n+2)/2

    散列表

    将线性表分成m个小表,构造hash函数,使名字均匀散布在m个子表中;若散列均匀,则时间复杂度会降到原线性表的1/m

    名字挂在两个链上(便于删除操作):

    • 散列链(hash link): 链接所有具有相同hash值的元素,表头在表头数组中
    • 作用域链(scope link):链接所有在同一作用域中的元素,表头在作用域链中

    操作:

    • 查找
      • 首先计算散列函数,然后从散列函数所指示的入口进入某个线性表,在线性表中沿hash link,像查找单链表中的名字一样查找
    • 插入
      • 首先查找,以确定要插入的名字是否已在表中,若不在,则要分别沿hash link和scope link插入到两个链中,方法均是插在表头,即两个表均可看作是
    • 删除
      • 以作用域链连在一起的所有元素从当前符号表中删除,保留作用域链所链的子表,为后继工作使用(如果是临时删除,则下次使用时直接沿作用域链加入到散列链中即可)

    散列函数的设计:

    1. 减少冲突,分布均匀
    2. 充分考虑程序设计语言的特点
      如:若有变量V001,V002,…,V300,且首字母的值作为hash值
    展开全文
  • 符号表

    千次阅读 2020-12-21 22:08:00
    目录符号表概述符号表的组织符号表的操作名字的作用范围栈式符号表 符号表概述 符号表的作用: 保存各类标识符的属性 检查语义的正确性:上下文敏感成分的分析实质上是语法分析的内容。但我们的语法分析是以上下文...
  • 编译过程和符号表重定位问题:转载至:点击打开链接 对于代码的编译问题千头万绪从何说起呢,首先来说一下计算机是如何处理应用程序的,实质上应用程序是通过操作系统来应用机器指令操控硬件设施完成各种任务的,...
  • 编译原理】部分题目+知识点

    千次阅读 多人点赞 2021-06-04 18:14:16
    词法分析阶段:该阶段主要建立符号表,将不重复的标识符,数字常数和字符常数的性质填入符号表中,并且将变量(常数)在符号表中的入口地址写到自身的token字语法分析阶段:主要使用符号表分析过程...
  • 平常的应用程序开发过程,我们很少需要关注编译和链接的过程,因为通常都是集成的开发环境下运行,因此一般编译和链接都是一步完成,通常将这种编译和连接合并到一起的过程称为构建。这样虽然简便,但是这...
  • 编译/连接/静态符号表杂谈1

    千次阅读 2011-08-10 22:24:27
    许多Visual C++的使用者都碰到过LNK2005:symbol already defined和LNK1169:one or more multiply defined symbols found这样的链接错误,而且通常是使用第三方库时遇到的。对于这个问题,有的朋友可能不
  • C++编译器符号表有哪些内容?

    万次阅读 2015-09-10 11:06:35
    其一是因为符号表在编译器的设计占有举足轻重的地位【我们学习编译原理的时候更多的是注重principles,而没有关心一个编译器的实现,所以符号表讲解的也比较少】,编译阶段的每“遍”都会和符号表打交道,本人只...
  • 一、Crash 类型 Crash 一般产生自 iOS 的微内核 Mach,然后 BSD 层转换成 UNIX SIGABRT 信号,以标准 POSIX 信号的形式提供给用户。NSException 是使用者处理 App ...二、iOS 的崩溃类型 ① EXC_BAD_ACCESS
  • 编译原理实验六】语义分析

    千次阅读 2018-12-23 16:25:27
    语义分析器 内容 学习经典的语义分析器 (1)选择一个编译器,如:TINY或PL/0,其它编译器也可(需自备源代码)。 (2)阅读语义分析源程序,...(3)理解符号表的定义(栏目设置)与基于抽象语法树的类型检查/...
  • !!! ...C++编译器符号表有哪些内容?...很早就想写一篇关于符号表的学习小结,可是迟迟不能...其一是因为符号表在编译器的设计占有举足轻重的地位【我们学习编译原理的时候更多的是注重principles,而没有关心一
  • 1.预编译阶段主要工作为: (1)#include 递归展开头文件 (2)#define 宏定义替换 (3)删除注释 (4)添加行符号文件标识 (5)保留#pragma(交给编译器处理) .c/.cpp文件经过预编译处理生成.i文件 ...
  • C,初始化发生代码执行之前,编译阶段分配好内存之后,就会进行初始化,所以我们看到C语言无法使用变量对静态局部变量进行初始化,程序运行结束,变量所处的全局内存会被全部回收。 而C++,初始化时...
  • [编译环境][gcc]内联函数

    千次阅读 2015-11-13 00:55:42
    gcc下的内联函数,inline、static inline与extern inline之间的区别。
  • 编译原理

    千次阅读 2021-06-07 06:49:04
    编译原理(计算机专业课程)编辑锁定讨论上传视频编译原理是计算机专业的一门...编译原理课程是计算机相关专业学生的必修课程和高等学校培养计算机专业人才的基础及核心课程,同时也是计算机专业课程最难及最挑战...
  • C++的编译过程详解

    千次阅读 2020-10-10 10:37:06
    编译、优化阶段 汇编过程 链接程序 一、编译预处理 (1)宏定义指令,如#define Name TokenString,#undef等。 对于前一个伪指令,预编译所要做的是将程序的所有Name用TokenString替换, 但作为字符串常量的 Name...
  • VSC/C++的编译流程(非常详细)

    千次阅读 2021-10-05 13:43:57
    编译过程就是将源文件的源代码翻译成机器语言,保存到目标文件。如果编译通过,就会把CPP文件转换成OBJ文件 一、.CPP文件和.OBJ文件 1. .CPP文件 每个cpp就是一个编译单元,每个编译单元相互之间是独立且相互...
  • 编译过程链接器的作用

    千次阅读 2019-06-02 00:24:22
    变量 对于变量而言,定义可以分为两种: 全局变量(global variables):其生命周期... 局部变量(local variables):生命周期只存在于函数的执行过程(即局部范围(local extent)),只能在函数内部访问 /...
  • C语言程序编译的几个阶段

    千次阅读 2018-02-04 19:19:04
    程序编译的过程就是将用户的文本形式的源代码(c/c++)转化成计算机可以直接执行的机器代码的过程。主要经过这么几个过程: 1、编译,由编译器将c源代码(.cpp)转变成汇编代码(.s) 2、汇编,由汇编器将汇编...
  • 程序编译的几个阶段

    万次阅读 2017-03-14 13:07:01
    程序编译的过程就是将用户的文本形式的源代码(c/c++)转化成计算机可以直接执行的机器代码的过程。主要经过这么几个过程: 1、编译,由编译器将c源代码(.cpp)转变成汇编代码(.s) 2、汇编,由汇编器将汇编代码...
  • g++编译详解

    万次阅读 多人点赞 2019-12-24 18:50:20
    g++编译详解 资料准备: 为了方便演示和讲解,这里提前准备好几个简单的文件:test.cpp test.h main.cpp 文件内容如下: main.cpp //main.cpp int main (int argc, char **argv) { return 0; } test.h //test....
  • 1 C 编译阶段

    千次阅读 2013-11-14 20:53:40
     目前Linux下最常用的C语言编译器是GCC(GNU Compiler Collection),它是GNU项目符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序.GCC不仅功能非常强大,结构也异常灵活.最值得称道的一点就是...
  • 我们各自的电脑上写下代码,得明白我们代码究竟是如何产生的,不想了解1,0什么的,但这几个环节必须掌握吧。 我们的代码会经过这4个环节,从而形成最终文件,c语言作为编译语言,用来向计算机发出指令。让程序员...
  • Java编译原理 什么是字节码、机器码、本地代码? 字节码是指平常所了解的 .class 文件,Java 代码通过 javac 命令编译成字节码 机器码和本地代码都是指机器可以直接识别运行的代码,也就是机器指令 字节码是不能...
  • 编译程序要对程序进行正确的翻译,首先要对程序设计语言本身进行精确地定义和描述。对语言的描述是从三个方面来考虑(精简地说): 语法:是对语言结构的定义(什么样的符号序列是合法的);定义语言的词法和语法的...
  • 编译过程和ELF文件

    千次阅读 2019-04-19 15:59:05
    目录 一、C/C++编译过程 二、ELF文件 ... 1、预处理:对源代码的伪指令(以# 开头的指令)和特殊符号进行处理,如#include指令,预处理会将对应的头文件(即.h文件,声明全局变量和函数,相当于jav...
  • 编译原理基本知识

    千次阅读 2020-12-24 19:36:47
    汇编指令:用符号表示的指令被称为汇编指令 汇编语言:汇编指令的集合称为汇编语言 2.语言之间的翻译 转换(也被称为预处理):高级语言之间的翻译,如FORTRAN到ADA的转换 编译:高级语言可以直接翻译成机器语言,也...
  • 程序编译+连接的简单过程

    千次阅读 2022-03-20 17:38:51
    ANSI C的任何一种实现(编译器),存在两个不同的环境。 第1种是翻译环境,这个环境源代码被转换为可执行的机器指令。 第2种是执行环境,它用于实际执行代码。 test.c要经过翻译环境变成可执行程序。可...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 43,237
精华内容 17,294
关键字:

编译阶段,函数保存在符号表中