前の記事 次の記事(工事中) メインページに戻る 

3-1 アドレスの決定とは

変数と関数

ソースプログラム上では変数は名前で管理されますが、オブジェクトプログラム(機械語)上では変数はアドレスで管理されます。
例えば a=b というソースプログラムに対応するオブジェクトプログラムには変数aのアドレスと変数bのアドレスが必ず含まれます。この様にコンパイラは単に構文の意味を解釈し、それに対応する機械語命令を選択するだけではなく、様々なアドレスを決定するという重要な役割も担っているのです。

また変数だけではなく関数(サブルーチン)もアドレスで管理されます。言語によっては他にも様々な要素(例えばクラスや構造体)をアドレスで管理するものもありますが、CΞコンパイラでは、変数と関数のみをアドレスで管理します。ここではCΞコンパイラの様にして変数(関数)のアドレスを決めているかを解説します。

3-2 オブジェクト領域の概要

オブジェクト領域は0100H番地からFFFF番地まで

 アドレスの決定方法の詳細の前に、RETROF-16の主メモリ配置について触れておきます。

 RETROF-16の主メモリのは番地から255番地までは仮想レジスタ領域ですので、CΞコンパイラはコンパイル開始直後に0番地に256番地への無条件ジャンプ命令を書き込みます。その後は中間コードを意味解析しながら256番地以降のメモリをデータ嶺域や機械語嶺域として費やしていきます。RETROF-16はスタック(あるいはスタック領域)という概念はありませんので、オブジェクト領域は主メモリ末尾であるFFFFH番地までです。それを超したら「メモリ不足エラー」となります。

3-3 実装方法

ObjectIndex

CΞコンパイラはオブジェクト(機械語)を生成するべき位置を保持する変数ObjectIndexを有しています。このObjectIndexの実体はunsignedshortで初期状態は0番地を示しています。

変数のアドレス決定

CΞ言語は、変数宣言を代入/参照よりも先に行う言語です。変数を宣言するver文が現れるとコンパイラは変数領域の必要サイズを算出し、そのサイズ分ObjectIndexを進めます。 同時に変数名とアドレスをアドレステーブルに登録します。このとき既に当該変数が登録されていたら、それは同一変数名の2重定義エラーとなります。

変数に対する代入・参照は、アドレステーブルを参照し、機械語のアドレス(オペランド)を決定します。このときアドレステーブルにアドレスが格納されていなければ「変数未定義エラー」となります。

関数のアドレス決定

関数のアドレス決定は、変数のそれに比べて多少厄介です。一般に「関数の呼び出し」と「関数の本体定義」はどちらを先に定義しても良い言語が殆どです。しかし、「関数の呼び出し」が「関数の本体定義」より先に現われると、とコンパイラから見ると突然未知の関数呼び出しが現れることになります。この場合はとりあえずアドレスの決定は保留し、定義が現れた時点でアドレスを埋め込むという方法が一般的です。しかし、極力単純なコンパイラを目指すCΞコンパイラでは、関数も変数と同様に定義を先に記述することをプログラマに強制することによりこの問題を回避することにしました。

これにより関数のアドレス決定は変数と同じ方法で処理が可能になります。その代償としてメイン関数とサブ関数がある場合、メイン関数を常に一番下に記述しなければいけないという若干辛い制約が生じることとなりました。

3-4 関数の戻り番地管理

リターンアドレス(戻り番地)の格納場所

RETROF-16はスタックエリアを有さないCPUです。このため、関数呼出は多くのCPUに見られるスタックにリターンアドレスを積む方法はできません。回避策としては特定のレジスタにリターンアドレスを格納する方法がありますが、この方法は関数呼出のネストが深まる都度、異なるレジスタを与える必要があります。他にも幾つかの方法がありますが、CΞコンパイラでは関数そのもののロジック領域の先頭に戻り番地を格納する方法を採用しました。この方法はコンパイラ自身のロジックが簡単になる反面、関数のロジック領域をROM化できないという欠点ふぁ有ります。また再帰呼び出しも不可能です。しかし主メモリ領域が全てRAMであるRETROF-16にとっては大きな問題ではありませんので、この方法を採用した次第です。

関数本体の機械語生成概略

CΞコンパイラは関数の本体定義が現れると、1ワード分の領域(戻り番地)を確保し、その後ろに関数のロジックを生成します。そして、関数呼出しが現れると、現在のプログラムカウンタの値を関数領域の先頭アドレス(戻り番地)を転送してから、関数領域の先頭アドレス+1番地に分岐します。関数からのリターンは、関数領域の先頭アドレスに間接分岐(指定アドレスのメモリ内容を分岐アドレスとする)することにより、メインルーチンに復帰します。


前の記事 次の記事(工事中) メインページに戻る