当前位置:   article > 正文

编译原理(十)——语义分析基础_语义分析·

语义分析·

一、语义检查的内容

1. 1语法和语义的区别

在这里插入图片描述

1.2 程序设计语言语义的分类

在这里插入图片描述

1.3 语义分析的功能

在这里插入图片描述

1.4 语义错误检查

首先可能要检查程序中标识符的声明问题,程序设计语言可以分为强类型语言弱类型语言。强类型:要求所有用到的表示符必须经过声明,而且在一个程序的作用域内只声明一次,因此需要检查有没有变量的声明、标识符有没有重复声明。

在这里插入图片描述

  1. 涉及到条件表达式的部分都要求是bool类型或者是逻辑值类型,大多数语言要求是一个逻辑值,现在很多语言用0或非0来进行判断,原来的语言一般要求是逻辑类型。
  2. 运算符是否相容,eg:x是int,y是float,x和y是相容类型,如果这个时候计算x+y会首先把x转换为float类型,然后再计算,但是如果x是int,y是bool则不能运算
  3. 赋值语句右边表达式的结果要和左侧的变量相容
  4. 数组一定要声明为数组类型,eg:使用a[i]的话,a就一定应该是数组类型
  5. 函数调用的时候参数的类型和数量要匹配

在这里插入图片描述


2.1 语义分析处理后的结果

在这里插入图片描述
处理后的是没有错误的token序列,token序列被分成3类:常量、标识符、特殊符。
newtoken和token相比,常量、特殊符都没有发生变化,但是标识符发生了变化。标识符在这里被分为两部分一部分仍然是原来的标识符另一部分指向一个符号表,符号表中还含有标识符的语义,记录了有关标识符的信息。

3.1 标识符在程序中的出现

在这里插入图片描述

  • 声明性出现:在程序的头部分、函数的头部分都要给标识符给予声明
  • 使用性出现:在程序和函数体部分出现,比如变量x是整型,程序里可以有x=x+1等,这种出现叫做使用性出现。

3.2 标识符的种类

在这里插入图片描述

  1. 常量标识符在很多语言中可以通过const n=100来定义,不能被赋值只能代表100的值,常量标识符不对应存储单元,而是对应一个值,所以不能被赋值
  2. 类型标识符即int、float啥的,在有的语言中通过type定义
  3. 变量标识符,在程序中用的最多的标识符,一般都要声明一些变量、数组等。变量又可以分为以下几类:
  • 实在变量:一般形式上声明的变量
  • 形参变量:形参是定义函数的时候需要,分为两类——值引用型形参、地址引用型形参
  1. 过函标识符,写程序的时候通常要定义一些过程和函数,所谓的过程就是没有返回值的函数,他们的名字就是所谓的过函标识符,也分成两类——实在过函(对应一个函数体或过程体)、形式过函(把过程和函数作为一个形参的形式表现出来,调用的时候对应的实参应该是过程和函数)
  2. 域名标识符,通常出现在记录中的域名,在C语言中对应的是结构体中的域名,声明一个记录的时候,要有很多的域,将域名单独列出作为一个独立的局部化区域来考虑后面的变量生存周期问题,所以把它单独拿出来作为一类标识符。

3.3 标识符的属性

在这里插入图片描述
在进行处理的时候一些语义信息要把他体现出来,要不然可能会出现一些错误,比如T是一个类型标识符,T+1的运算从语法的角度来说标识符加常数没有错误,但是语义上有错误,所以一个标识符的语义信息要把他的一些主要的内容区分开。
这些信息缺一不可,缺少某一项翻译工作无法进行

二、标识符的语义表示

1. 常量标识符

在这里插入图片描述

TypePtr执行类型信息表的指针
Value可以直接写上具体的值,也可以一个指向常量表的指针
常量标识符不能被赋值,因为在内存中不保存常量标识符,实际上常量标识符是不存在的(不在内存中分配空间,因为已经在编译的时候把常量全部替换成真正的“常量”了)
在这里插入图片描述


2. 类型标识符

在这里插入图片描述
类型本身不对应内存空间的
在这里插入图片描述

intPtr指向类型信息表中的int部分。t2指向内存中的数组类型,而数组类型中的每个元素的基本类型再指向int类型


3. 变量标识符

在这里插入图片描述
Access:直接取址还是间接取址(存的是地址还是值)
Level:层数,实际上与语言是嵌套式语言还是并行式语言相关
每一个函数在程序真正要运行的过程中要对应一块存储单元,我们把这个存储单元叫做过程活动记录。定义的函数活动记录存储在Activity Record(AR)中(一般程序中会将内存按照一定的规则划分,划分成常量区、堆栈区等,AR即存储在栈区,进行到一个函数就将它的AR存储到栈区。后面需要一个变量的Level来对使用性变量进行说明。
Off:表示当前过程中的变量存储在AR的哪部分,对应内存块的起始位置的偏移量

int g(param seris){
//在函数名之前的属于L层,则后面括号中的参数一直到下一个函数的函数名都是L+1层,依次类推k
    int i;
    int g1(){
        int j;
        int g2(){
        int k = i+j;
        }
    }
}
//这里一般在最内层函数中是可以调用外层函数中的变量的
//这里f和g属于并列关系,g和g1、g2属于嵌套关系
//g函数可以调用f函数但是不能调用f1和f2,不能调用定义在函数f里面的函数
int f(){
     ...
     int f1(){
     ...
         int f2(){
         ...
         }
     }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

在这里插入图片描述


4. 域名标识符的内部表示

在这里插入图片描述
偏移量是针对记录类型的偏移量,一个记录可能有多个域,每一个域都要占用一定的存储单元,而这个偏移就是我们这个域名针对记录的偏移,域名的宿主类型,因为一个域在不同的记录中都可能声明,比方说学生可以声明一个name,工人声明一个name,一旦出现一个name,我们需要知道他是哪个记录中定义的,所以要保存它的宿主类型。


5. 过程/函数标识符的内部表示

一个函数被调用的时候本身就是一个AR,Off部分为nullptr
正常函数没有Off,为默认值nullptr,但如果是虚参函数,作为另一个函数的参数存在,这个时候Off不为nullptr
在这里插入图片描述
在这里插入图片描述
Code:函数目标代码的地址,在函数调用之后一定需要一个语句返回函数体内的固定位置,需要知道目标代码的初始位置。创建符号表的时候,目标代码的地址是不确定的,但是空间要保留出来,待后续确定之后写回Code。如果要形成一条转移指令,则到Code部分找到目标地址回填。
在这里插入图片描述
Size:存储AR表的大小,Size多大对应天多大的内存空间,但是这里存在一个问题就是实际上所需的内存空间不是程序中定义的变量等需要的那么大的内存空间,实际上在编译过程中(未涉及优化)会产生大量的中间变量,中间变量也需要内存空间,需要动态调整,根据生成的中间变量的大小增加Size的值。

int  p = k+i+j;
//首先取i的值送入Reg,然后和j相加送入中间变量t1
//然后将t1值取出放入Reg,然后和k相加送入中间变量t2
//都得先放入Reg(考虑一般情况)
//这里的t1和t2就是临时变量
  • 1
  • 2
  • 3
  • 4
  • 5

Forward:前向声明

int f1(){
    g1();
}
int g1(){
    f1();
}
//这里函数f1和g1都是函数定义,但是定义都不完整,无法直接进行二者的调用
//使用前置声明可以解决这个问题
//前置声明只包括函数名和返回值以及参数不包括函数体
//只是在未完全定义时写在所有使用的地方前面,则可以使用未定义函数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述

三、类型的语义表示

类型信息有什么作用呢?他的作用主要有两类:
1、做类型的检查,不同类型的数在计算机中的表示形式是不同的
2、要根据类型的大小为每一个变量分配存储空间的,一个简单变量和一个数组他们占用的存储空间的大小是不同的
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
枚举类型是一种特殊的数据类型,定义的数据通过枚举的方式都把它给出来,
枚举类型的语义信息,元素的大小,以后这种枚举变量,给他开多大的空间,
第二就是种类表示他是一个枚举类型,
第三他要按照表的方式给他存放起来,要记录一个表头,后面是长度表示枚举类型应该有几个元素。
(内部计算的时候枚举的内容按整形存储)
在这里插入图片描述
在这里插入图片描述

Size = (up-low+1)* Typelength

一维数组和二维数组的表示如下:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
按照最长的类型size开辟空间
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
集合类型,大小 种类,基类型,这个集合是定义在哪一个类型上面的。集合的大小怎么来确定,因为集合可能是一个无穷极,怎么来确定他的大小呢?程序设计语言中设定一个集合的时候都是设定成一个有限的集合,用2的n次方来确定,假如有1000个元素是不是要给他1000个存储单元呢? 用一个二进制码来表示这个集合,按位来表示,所以集合变量占用的空间并不是很大,好处是关于集合的运算就很好做了,集合的并就是二进制数的逻辑加,交就是逻辑乘

文件类型,大小指的是缓冲区的大小, 通常文件是这样处理的,数据文件是每次读入是按照缓冲区的大小读入一个记录,缓冲区类型
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/599367
推荐阅读
相关标签
  

闽ICP备14008679号