我们在这篇文章中只讨论静态数组的翻译,也就是在编译之前就已经确切知道大小的数组。并且我们把数组的下界定为1,也就是说数组下标是从1开始逐渐增长的。
A(1,1) | A(1,2) | A(1,3) | A(1,4) |
A(2,1) | A(2,2) | A(2,3) | A(2,4) |
A(3,1) | A(3,2) | A(3,3) | A(3,4) |
我们用上面这个A[3][4]数组来举例子,该数组存储的第一个字(或者是满足该数组数据结构的第一个存储单元)的初始地址就是A(1,1)的初始地址。
PS:对于存储单元的解释,比如在C语言中,int型的数组就是给每个单元分配32个bit的存储空间,而如果是结构体数组,并且这个结构体中有2个int型变量的话,那么这个数组每个单元就会分配2*32bit的存储空间
在编译的时候,我们假定我们是按行来存放数组元素的,如下图所示
A(1,1) |
A(1,2) |
A(1,3) |
A(1,4) |
A(2,1) |
A(2,2) |
A(2,3) |
A(2,4) |
A(3,1) |
A(3,2) |
A(3,4) |
A(3,4) |
假设A为数组的初始地址,那么假如我们要求A[2][1]的地址的话就可用
a+(2-1)*4+(1-1)
来表示。
推广开来,对于一个n维数组A[l1:u1,l2:u2,l3:u3,...](其中l1表示第一维的下界,u1表示第一维的上界),在我们的例子中,l1就是1,u1就是3,l2就是1,u2就是4。
就可以表示为地址D:
D = A + (i1 - l1)d2d3...dn+(i2 - l2)d3d4...dn+...+(in-1 - ln-1)dn + (in - ln)
在我们的例子中求A[i1][i2]的地址D就可以表示为:
D = A + (i1 - 1)*d2 + (i2 - 1)
其中d2表示数组第二维的长度也就是4.广义上的计算公式为:
di = ui - li +1
在上面计算地址D的公式中 l1 - ln 和 d1 - dn 都是在编译之前已经确定好的并且他对任何一个数组成员的地址来说都是不变的,也可以认为是数组地址的基址,所以他其实可以表示为常数CONSPART,而 i1 - in 是用户在写程序的时候根据需要随机确定的,所以他是可变量。由此我们又可以将地址写成
D = CONSPART + VARPART
CONSPART = A - C
C = (...((l1d2+l2)d3+l3)d4+...+ln)dn+ln
VARPART = (...((i1d2 + i2)d3+i3)d4 + ... + in-1)dn + in
上面就是广义上的数组地址求法,对于任意有限维的数组都适用。
下面我们介绍数组结构在编译的时候的翻译过程:
数组在编译的时候需要用到两张表,第一张叫做符号表,他的结构如下:
名称 | 特性 | ... | 地址 |
---|---|---|---|
A | 数组 | ... | (信息向量表入口地址) |
第二张表叫做信息向量表,也就是符号表地址栏指向的表,他的结构如下:
l1 | u1 | d1 |
l2 | u2 | d2 |
... | ... | ... |
ln | un | dn |
n | C | |
type | A |
其中C为上面我们求的地址中不变量用到的常数,A直接指向内存单元中数组的头地址,也就是第一个元素在内存中的初始地址。
n为维数。
并且我们规定数组赋值的四元式表示为:
变址取数:X := T1[T] ( =[ ] , T1[T] , - , X )
变址存数:T1[T] := X ( [ ]= , X , - , T1[T] )
接下来介绍赋值语句中存在数组元素的时候用到的的文法:
文法G:
A -> V:= E
V -> elist] | i
elist -> elist , E | i[E
E -> E+E | (E) | V
最后通过一个例子来介绍翻译过程中用到的语义动作:
例题:A是一个10*20的数组,写出 A[I+2,J+1] := M+N 的翻译过程
首先 A [ I+2 入栈,这时候进行规约
1.E1 -> I + 2
{ T1 := NEWTEMP; //申请一个新的临时变量T1,用来存放规约后的E的值
GEN(+ ,i ,2 ,T1);
E1.PLACE := T1; //将E符号表的地址部分填为T1的地址
}
这时候 堆栈里面剩下的是 A [ E,将其规约为ELIST
2.{
ELIST.PLACE := T1;
ELIST.DIM := 1; //维数信息填为1
ELIST.ARRAY := ENTRY(A); //将数组A的初始地址填入数组名符号表中的地址部分
}
这时候,堆栈里面进入了 ‘,’ 还有 J + 1,我们要把J+1进行规约,其过程与1类似
3.E2 -> J+1
{
T2 := NEWTEMP;
GEN( + ,J , 2 , T2);
E2.PLACE := T2;
}
接下来我们需要把堆栈中的 ELIST , E 规约,这一步规约要得到数组地址的可变部分VARPART应用公式
VARPART = i1d2 + i2 ,其中i1 为T1,i2 为T2
4.ELIST -> ELIST1 , E
{
T3 := NEWTEMP;
K := ELIST1.DIM + 1; //维数加一
dk := LIMIT(ELIST1.ARRAY , K); //取数组A的第K维
GEN( * , ELIST1 .PLACE, dk, T3);
GEN(+ , E2.PLACE , T3 , T3);
ELIST.ARRAY := ELIST1.ARRAY;
ELIST.PLACE := T3;
ELIST.DIM := K;
}
接着堆栈中进了 ] ,我们需要把 ELIST ] 进行规约,到这一步我们可以求CONSPART了,用的公式为
CONSPART = A - C,假设C在我们做这一步之前已经计算好了,这道题目为21 = 20 + 1:
5. V -> ELIST ]
{
T4 := NEWTEMP;
GEN(- , ELIST.ARRAY , C , T4);
V.PLACE := T4;
}
之后堆栈进了 := M + N ,我们要对M + N 进行规约:
6.E3 -> M + N
{
T5 := NEWTEMP;
GEN(+ , M , N ,T5);
E3.PLACE := T5;
}
最后我们对赋值语句进行规约:
7.A -> V := E3
{
IF(V.OFFSET = NULL) THEN
GEN(:= ,E.PLACE , - ,V.PLACE); //V.PLACE表示数组第一个元素A的地址
ELSE
GEN(:= ,E.PLACE, - , V.PLACE[V.OFFSET]); //V.OFFSET表示的是可变地址部分
}
最后总结一下产生的四元式:
1. (+ ,I , 2, T1)
2. (+ , J , 1 , T2)
3. (* ,T1 , 20 ,T3)
4. (+ , T2 , T3,T3) //T3存储的就是V.OFFSET值
5. ( - ,A , 21 ,T4)
6. (+ , M , N , T5)
7.([ ]= ,T5, - , T4[T3] ) //变址存数
参考书:《编译原理(第二版)》清华大学出版社
例题来源:西安交大《编译原理》网络公开课