先週は、
Cでは、以下のポインタ演算が可能である。
1183 case MUL: 1184 getsym(); 1185 e=rvalue(expr13()); 1186 return(indop(e)); 1187 case BAND: 1188 getsym(); 1189 switch(car(e=expr13())) 1190 {case INDIRECT: 1191 e=cadr(e); 1192 break; 1193 case GVAR: 1194 case LVAR: 1195 e=list2(ADDRESS,e); 1196 break; 1197 case FNAME: 1198 return e; 1199 default:error(LVERR); 1200 } 1201 type=list2(POINTER,type); 1202 return e;indop()では以下のように中間木を生成する。
1368 indop(e) 1369 int e; 1370 { if(type!=INT&&type!=UNSIGNED) 1371 if(car(type)==POINTER) type=cadr(type); 1372 else error(TYERR); 1373 else type= CHAR; 1374 if(car(e)==ADDRESS) return(cadr(e)); 1375 return(list2(INDIRECT,e)); 1376 }
array と struct は、アドレスを考えれば、統一的に扱うことができる。 例えば、a[4]というのは、a+4 というアドレスだし、a->type というのは、 a の指し示す構造体ののアドレスにtypeというoffsetを足したアドレス である。実際に、expr16()では、indop()を使ってarrayとstructの 中間木を作っている。Cから見た時には、すべてはアドレスと、その 指し示す先の大きさでしかない。大きさは型から決まる。
1312 expr16(e1) 1313 int e1; 1314 {int e2,t; 1315 while(1) 1316 if(sym==LBRA) 1317 { e1=rvalue(e1); 1318 t=type; 1319 getsym(); 1320 e2=rvalue(expr0()); 1321 checksym(RBRA); 1322 e1=binop(ADD,e1,e2,t,type); 1323 e1=indop(e1); 1324 } 1325 else if(sym==LPAR) e1=expr15(e1); 1326 else if(sym==PERIOD) e1=strop(e1); 1327 else if(sym==ARROW) e1=strop(indop(rvalue(e1))); 1328 else break; 1329 if(car(e1)==FNAME) type=list2(POINTER,type); 1330 return e1; 1331 } ..... 1377 strop(e) 1378 { getsym(); 1379 if (sym!=IDENT||nptr->sc!=FIELD) error(TYERR); 1380 if (integral(type)||car(type)!=STRUCT && car(type)!=UNION) 1381 e=rvalue(e); 1382 type = nptr->ty; 1383 switch(car(e)) 1384 {case GVAR: 1385 case LVAR: 1386 e=list2(car(e),cadr(e) + nptr->dsp); 1387 break; 1388 case INDIRECT: 1389 if(!nptr->dsp) break; 1390 e=list2(INDIRECT,list3(ADD,cadr(e),list2(CONST,nptr->dsp ))); 1391 break; 1392 default: 1393 e=list2(INDIRECT,list3(ADD,e,list2(CONST,nptr->dsp))); 1394 } 1395 getsym(); 1396 return e; 1397 }
これらの中間木はgexprによってコード生成される。
1663 gexpr(e1) 1664 int e1; 1665 {int e2,e3; 1666 if (chk) return; 1667 e2 = cadr(e1); 1668 switch (car(e1)) 1669 {case GVAR: 1670 leaxy(e2); 1671 return; 1672 case RGVAR: 1673 lddy(e2); 1674 return; 1675 case CRGVAR: 1676 ldby(e2); 1677 sex(); 1678 return; .... 1829 case ASS: case CASS: 1830 assign(e1); 1831 return; 1832 case ASSOP: case CASSOP: 1833 assop(e1); 1834 return;GVARがleft value、RGVARがright valuewを表している。代入文は、 さらに、assign()というコード生成手続きを呼び出す。
2014 assign(e1) 2015 int e1; 2016 {char *op; 2017 int e2,e3,e4,e5,l; 2018 op = (car(e1) == CASS ? "STB" : "STD"); 2019 e3 = cadr(e2 = cadr(e1)); 2020 e4 = caddr(e1); 2021 switch(car(e2)) 2022 {case GVAR: case LVAR: 2023 gexpr(e4); 2024 index(op,e2); 2025 return; 2026 case INDIRECT: 2027 switch(car(e3)) 2028 {case RGVAR: case RLVAR: 2029 gexpr(e4); 2030 indir(op,e3); 2031 return;ここで、index()がSTBやSTDを生成していることが分かる。indir()では、 LDB [,X] なdが生成されて6809のindirect addressing を有効に使用 している。
binop(), assign() などは、さらに細かい処理を行っているので、 このあたりを読んで見るとcompilerの構文木の生成と、コード生成の 実際が良く分かるはずである。特にアドレスの足し算、引き算では、 特別な扱いをしていることに注意しよう。
* int *a; a EQU 0 * main() * { * * *a = 1; main PSHS U LEAU ,S * generate code on type: * list(INT) * expr: * list(ASS, * list(INDIRECT, * list(RGVAR,0)), * list(CONST,1)) LDD #1 STD [0,Y] * a = &a[3]; * generate code on type: * list(POINTER, * list(INT)) * expr: * list(ASS, * list(GVAR,0), * list(ADD, * list(RGVAR,0), * list(CONST,6))) LDD 0,Y ADDD #6 STD 0,Y * *a = *(a + a[3] + 2); * generate code on type: * list(INT) * expr: * list(ASS, * list(INDIRECT, * list(RGVAR,0)), * list(RINDIRECT, * list(ADD, * list(ADD, * list(MUL, * list(RINDIRECT, * list(ADD, * list(RGVAR,0), * list(CONST,6))), * list(CONST,2)), * list(RGVAR,0)), * list(CONST,4)))) LDX 0,Y LDD 6,X ASLB ROLA ADDD 0,Y PSHS D LDD #4 PULS X LDD D,X STD [0,Y] * a = *( &a + 3); * generate code on type: * list(POINTER, * list(INT)) * expr: * list(ASS, * list(GVAR,0), * list(RGVAR,6)) LDD 6,Y STD 0,Yどうして、このようなコード出力になるのかは木構造が分かってしまえば、 gexpr() をトレースするだけの問題である。この木構造は、最後の例では 単純な構文木 とは少々異なる。それは、indirect()や、rvalue() などが構文木を変換 しているからである。
&a = (int *)3; というのは、Micro-C ではエラーになる。なぜ、エラー なのかを説明せよ。
実際にコンパイラを走らせてみれば、
9:Lvalue required. &a = (int *)3; ^というエラー出力される。このエラーメッセージを確認しないで答えを出そうと言う のは、少し虫が良すぎる。コードの(int *)3の部分にはエラーはない。実際、
* a = (int *)3; * generate code on type: * list(POINTER, * list(INT)) * expr: * list(ASS, * list(GVAR,0), * list(CONST,3)) LDD #3 STD 0,Yという形にコンパイルされる。問題は明らかに&aの部分にある。しかし、 前の授業でも説明したように、構文的なエラーはない。このエラーは代入文 (assignemnt)に特有なエラーである。実際、Lvalue required というエラー は、
257 (n==LVERR) ? "Lvalue required" :であり、LVERR は、expr13の&の部分と、
1363 lcheck(e) 1364 int e; 1365 { if(!scalar(type)||car(e)!=GVAR&&car(e)!=LVAR&&car(e)!=INDIRECT) 1366 error(LVERR); 1367 }に出てくる。expr13()ではなくlcheck()でエラーになっているのはトレース してみればすぐにわかる。この場合は、
994 expr1() 995 {int e1,e2,t,op; 996 e1=expr2(); 997 switch (sym) 998 {case ASS: 999 lcheck(e1); 1000 t=type;ここのlcheck()でエラーになるわけである。実際、lcheck では、左辺値は、 整数(scalar)か、大域変数(GVAR)か、局所変数(LVAR)か、間接参照であるとし ている。&a は、そのどれでもないのでエラーとなる。(ANSI-C では、 この他にも、いくつか左辺値を許している。どのようなものがあるだろうか?)
今週は、宿題はありません。