一个标识符的语义信息含有三部分内容,种类 类型 抽象地址 种类可以分为:常量标识符、类型标识符、变量标识符、域名标识符、过函标识符 类型可以有:基本数据类型、子界、数组、枚举、结构体、集合、文件、指针等等
语义分析:
静态分析——静态语义动态分析——动态语义核心:
程序中是否有语义错误在语义分析阶段要构建一个符号表为整个后序过程提供支持在进行编译的时候为每一个变量标识符都分配一个相对地址,当目标程序在真正运行的时候,一旦首地址确定下来,那么所有的变量对应的地址就可以确定下来了,这是一种比较简单直观的方法,例如fortune语言就是用这样的方式,但是也有其局限性。比如有动态申请地址空间的时候,这种方法不适用。
两个比较典型的例子:
指针变量,他是在程序的运行过程中可以动态的申请存储空间,申请空间的次数和占有多大空间,这个在程序没有执行的时候是没有办法确定的,如果存在于程序的某一个分支或者循环中,程序没运行则没有办法确定。既然没有办法确定,就没有办法预先分配存储空间。程序中的递归函数,这是一个比较有特点的调用形式。每次调用一个函数都要给它的形参和内部的变量开辟对应的存储空间,在函数被递归调用(最外面那个函数被调用)的时候,一个局部变量比方说x的时候他对应的就不是一个空间,而是一串的存储空间,这一个变量占用多少空间取决于一个递归程序执行的次数,静态的时候没有办法确定次数,因为影响次数的因素非常多,比方说跟输入有关、跟什么条件有关,所以没有办法静态的确定到底占多大的空间,这样的情况也不能采用静态的方式来分配。只能用动态的方式,给变量在程序中分配的不是一个(程序中变量分配的地址)不是一个具体的地址,而是一个抽象地址。一个变量对应的存储单元现在是不确定的只是一个抽象的地址,在真正的目标程序运行的时候,再把这个地址映射到一个实际的物理地址上,这样的话,需要多少,就可以动态的分配多少,通过抽象地址进行映射,得到所需要的存储空间。现在大多数语言,因为有动态的数据结构,有递归,所以都采用的是动态的存储空间形式,动态的管理模式。
这是两种最常用的地址分配方式。现在主要采用动态的方式
对于并列式语言这个层数是不是没有意义呢? 其实也不是,只是这个层数相对更为简单。比如把全局的变量定义成0层,函数中的变量定义成1层。
偏移: 每次函数被调用的时候,都会给函数分配一片空间,包括一些管理信息,形参、局部变量、临时变量要占据存储单元,偏移量是针对于这个起始地址而言,他的偏移是多少,以后只需要知道起始位置在哪,就可以找到对应变量所在的单元。起始位置+偏移量就是对应的一个物理地址,可以通过这样的形式把它表示出来
过程活动记录:一个函数或者一个过程在他真正调用的时候会占用一片存储空间,我们把这片存储空间称作过程活动记录,表示一个函数被真正调用时,它所占用的存储空间,这个存储空间中包括的信息主要有: 管理信息(返回地址、寄存器现场的信息等等的管理信息,这些信息占用的空间大小是固定的,对于一个具体的目标机而言,这个是固定的)
形参区,在真正函数调用的时候也要有具体的存储单元,所以也要给他们分配具体的存储单元,比方说整型的形参分多少、数组的分多少。 局部变量区是指在函数内部声明的变量所占用的空间在这一块区域分配。
临时变量区,在生成目标代码之前,可能会产生很多的中间计算结果,中间的结果全都存在寄存器中恐怕是做不到的,因为有的时候结果比较多,寄存器的个数是有限的,所以不能全都存在寄存器里,有一部分要存在内存中,既然是存在内存里,就要给分配一块存储空间,这里就是存的这种临时变量。
这就是一个函数在调用的过程中所要占用的空间,这里的存储顺序为什么是按照这个顺序排列的呢?这个是按照处理的顺序分配的,比方说管理区是固定的,是函数调用的时候大小固定, 然后先处理的是形参,从函数头开始处理,所以给形参分区,然后是局部变量,然后是临时变量。这是过程活动记录的大概的结构.
一般使用顺序查表法。
函数,从函数名后面开始就进入了局部化区,函数结束退出这个局部化区。 另外一个是域名是一种特殊的局部化区,记录变量在进入记录之前,也是有一个局部化区,特殊的情况就是域名和变量名可以相同,但是由于有域名,就跟一般的变量进行了区分。 不同的语言在约定上不太相同,c语言中的分程序结构用{}的部分也是局部化区,在{}可以再声明一些变量和标识符,出了这个花括号就没了,
在一个局部化区的时候,要考虑一些语义上的问题,主要考虑的是两类语义错误的问题,第一大类的语义错误就是变量的重复声明,第二大类就是变量没有声明的使用。 第一类问题:变量的重复声明。 在一个程序的局部化区里,同一个标识符不能被声明两次 第二类问题:标识符的使用没有声明,现在的程序设计语言都属于强类型语言,也就是说所有的标识符都要给出对应的声明,没有声明出现的标识符认为就是有错的。比方说(),在某些弱类型的语言中也可以不用这么声明,比方说qbasac。
作废方法:
直接删除,直接将退出的局部化区的全部内容从符号表中删除,但是这对于后续维护等操作来说会带来困难在退出的位置开辟一个新的空间存储一个指向当前局部化区最开始位置的一个指针,后面的变量在查找的时候从下往上找会略过这个区域,但是注意,返回的是局部化区的开始而不是完全跳出,因为这样还可以在当前位置再次跳入该局部化区。答案在下一节笔记上。
