先週は、
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 では、
この他にも、いくつか左辺値を許している。どのようなものがあるだろうか?)
今週は、宿題はありません。