SCOOT C-Compiler v1.00 1996/2/14 小笠原博之 100% オリジナルの Cコンパイラです。C言語とはいいつつ、実数演算や構造体を持 たないなど、仕様にはある程度の制限があります。 このプログラムはコンパイラ本体とコンパイルドライバのみ含まれており、プリプ ロセッサを含んでいません。プリプロセッサ cpp は他のコンパイラのものを使用し て下さい。 『基本的な使い方』 ●使い方 scoot [オプション] ファイル名 [..] 与えたファイルをコンパイルし、最終的に実行ファイルを得ます。登録された拡張 子によりそれぞれ必要なコマンドを順次呼び出します。 <オプション> -o 出力ファイル名を与えます。出力ファイル名を指定しなかっ た場合は、出力は1番最初に与えたファイルの拡張子を変更 したものになります。 -c オブジェクトファイルを出力します。(-y8と同じ) -S アセンブラソースを出力します。(-y12と同じ) -E プリプロセッサのみ実行します。(-y16と同じ) -O[] オプティマイザを呼び出します。 -I インクルードファイルを検索するディレクトリを与えます (オプションとの間にスペースを入れない)。 -D[=] プリプロセッサのシンボルを定義します。 -U プリプロセッサのシンボルを削除します。 -v コンパイルの実行経過を表示します。 -m コンパイラ内蔵のコードジェネレータを選択します。現在 0=68000(short-int),1=dump,2=SC62015 の3つを内蔵して います。(default -m0) -A オプション文字列をアセンブラにそのまま渡します。 -W オプション文字列をコンパイラにそのまま渡します。 -R スタート時のルールセットを選択します。(default -R0) -y ルールセットの停止レベルを選択します。(default -y4) -z SC62015(ESR-L)用のオブジェクトを出力します。これは -m2 -R7 -y8 を与えたのと同じです。(-O時は-R11) <例> ZAURUS 用のプログラムを作る場合の例 scoot -z test.c 『コンパイルドライバについて』 ●コンパイルドライバ scoot の詳細 ・ ターゲットが SC62015(ESR-L) の場合(内蔵ルールセット7/11) -O オプションがない場合(ルールセット7の場合)はオプティマイザを呼び出しませ ん。-O オプションがある場合はルールセット11が選ばれます。 .c C言語のソース .ss アセンブラソース .sx オブジェクトファイル .pp プリプロセッサで処理済みのC言語のソース(内部処理用) .sa オプティマイズ前のアセンブラソース(内部処理用) .inc インクルードライブラリ [入力] (.c) (.pp) (.sa) (.ss/.inc) ↓ ↓ ↓ ↓ プリプロセッサ─→コンパイラ─→オプティマイザ─→アセンブラ─┐ | | | -Eオプション -Sオプション 実行ファイル ↓ ↓ ↓ [出力] (.pp) (.ss) (.sx/.BAX) 環境変数 sc_include に、ヘッダやライブラリの include ディレクトリを設定す ることができます。また sc_opt に、opt.pl へのパス名を登録できます。 『SCOOT C-Compiler の言語処理マニュアル』 ●C言語からの制約 (1) 使用できるデータタイプに制限があります。まず構造体がなく、struct, union, enum が使用できません。また typedef による派生型もありません。 (2) アドレス計算時のサイズが char, short, long に限定されています。よって、 ・2次元以上の配列が使えません。(ただしポインタの配列、ポインタのポインタな どによって a[x][y][z] .. のように記述することは可能) ・配列そのものをさし示す配列のポインタが使用できません。 ・sizeof 配列変数名 の場合に、配列全体ののサイズを得られません。 (3) 実数が使えません。計算は整数のみです。 (4) 変数の型変換やプロトタイプ宣言など、エラーチェックが厳密ではありません。 (5) まだ対応していない機能や構文があります(対応未定) (6) バグが多数潜んでいる可能性があります。 ●演算範囲 各型のサイズや範囲は、ターゲットとなる CPU によって異なります。ただし どの型も、必ず char, short, long の基本型のどれかと同じ大きさであり、 またこれら 3種類の大きさの型しか使用できません。 <例> 68000 (int=long) 68000 (int=short) SC62015(ESR-L) char 1byte char 1byte char 1byte short 2byte short 2byte short 2byte long 4byte long 4byte long 3byte(20bit) int 4byte int 2byte int 2byte pointer 4byte pointer 4byte pointer 3byte(20bit) ●変数の型と記憶クラス 基本型の宣言は以下の5種類と、それに unsigned または signed をつけた ものです。何もつけない場合は signed と同じです。 [signed|unsigned] int [signed|unsigned] char [signed|unsigned] short (short int という記述はできない) [signed|unsigned] long (long int という記述はできない) void 記憶クラスとして extern, static, auto, 無指定 があります。これらは使 用する場所により、次のような違いがあります。 ・トップレベルでの記憶クラス extern 外部参照(データは外部にあるものと見なし確保はしない) static メモリに割り付け、スコープをソース内に制限する 無指定 メモリに割り付ける。外部から参照可能 ・ブロック内部での記憶クラス(スコープは常にブロック内部である) extern 外部参照(データは外部にあるものとみなし確保はしない) static メモリに割り付ける(関数を抜けてもデータを保持) auto スタック上に確保する(関数内部で毎回作られる) 無指定 autoと同じ 記憶クラス auto の一種に register 宣言があります。register 宣言され た変数は、可能なら変数をレジスタに割り当てます。レジスタに割り当てで きない場合は通常の変数と全く同じものになります。割り当て可能なレジス タの個数は CPU によって異なります。当然ながら、レジスタに割り当てら れた変数のアドレスを取ることはできません。 以下のキーワードは予約語として存在していますが、使用しても無視します。 const, volatile, typedef, float, double 以下のキーワードも予約語ですが、使用するとエラーになります。 struct, union, enum ●定数 定数は以下の6種類が存在します。実数(浮動小数点定数)はありません。 2進定数 0b0〜0b11111111〜0b1111111111111111〜 8進定数 0〜0377〜0177777〜 10進定数 1〜255〜65535〜 16進定数 0x0〜0xff〜0xffff〜 文字定数 '\0' や 'a'〜'z', '\n' など 文字列定数 "〜文字列〜" ・2〜16進定数 2進定数はこのコンパイラ独自の拡張です。いずれも数値の最後に 接尾子を付けてデータサイズを指定することができます。省略時は int です。 <接尾子> L または l = long U または u = unsigned (実際は無視しintと同じ) W = short固定 T = char固定 U は他の処理系では unsigned int になりますが、このコンパイラ ではつけてもつけなくても特に差はありません。接尾子の W と T はこのコンパイラ独自の拡張です。 ・文字定数 char 型です。ただし前に L をつければ、複数の文字をまとめたり 幅広文字を記述することができます。この場合 short 型と見なさ れます。 <例> L'AB' L'漢' このコンパイラでは、実際はlongにキャストすれば3文字以上の文 字定数も使えます。 ・文字列定数 文字列は常にメモリへ割り付けられ、その char へのポインタを型 に持ちます。文字列定数の最後には必ず '\0' が含まれています。 隣接する文字列定数は、合せて1つの文字列であるとみなされます。 <例> "abc" "def" → "abcdef" と同値 また文字定数同様最初に L を付けることにより、幅広文字(実際は short)へのポインタにすることができます。 ・文字エスケープ 文字定数または文字列内部で使用可能なエスケープキャラクタは次 の通りです。 \n LF (0x0a) CTRL+J 改行 \r CR (0x0d) CTRL+M 復帰 ([RETURN]) \t HT (0x09) CTRL+I タブ ([TAB]) \v VT (0x0b) CTRL+K \a BEL(0x07) CTRL+G \b BS (0x08) CTRL+H バックスペース ([BS]) \f FF (0x0c) CTRL+L 改頁 \x 任意の文字 16進数 \ 任意の文字 8進数 \\ 文字「\」 \" 文字「"」 \' 文字「'」 ●演算子 ↑ () [] → 括弧,配列 高 + - ++ -- ~ ! & * (型) sizeof ← 単項,アドレス * / % → 2項 + - → 2項 << >> → 2項 < > <= >= → 2項 == != → 2項 & → 2項 ^ → 2項 | → 2項 && → 2項 || → 2項 ?: ← 3項 低 = += -= *= /= %= &= |= ^= <<= >>= ← 代入 ↓ , → カンマ <補足> ・gcc と同じように、? : 演算子で2番目の項を省略できます。省略した場 合条件式の値を取ります。 (この仕様は将来削除する可能性あり) ●変数定義 変数の定義は、トップレベルかもしくは各ブロックの先頭で行うことができ ます。トップレベルの場合スコープ(参照)は宣言以後ソース内で可能で、ブ ロック内部の場合は、そのブロック内のみに制限されます。 記憶クラス 変数の型 変数名 =初期値 , 〜 ; 記憶クラス 変数の型 変数名[要素数] = { 初期値 ,.. } , 〜 ; (など、その他いろいろな形式がある) 変数宣言にはポインタ指定 '*' や配列、または関数呼び出しや関数のポイ ンタを指定することができます。 <例> int *a, /* intへのポインタ宣言 */ b[10], /* intの配列宣言 */ c(), /* intを返す関数宣言 */ (*d)(), /* intを返す関数へのポインタ宣言*/ **e; /* intへのポインタのポインタ宣言*/ 宣言時に変数への初期値を与えることができます。ただしトップレベルや記 憶クラスが static の変数では、初期値は定数でなければなりません。auto の変数の場合は任意の式を与えることができます。auto の配列では初期値を 与えることはできません。配列の要素数は必ず定数でなければなりません。 配列に初期値リストがある場合は要素数を省略できます。char の配列の場合 のみ、文字列で配列の初期化ができます。 <例> char a[]= "COR."; ●関数定義 記憶クラス 関数の戻り値の型 関数名 ( 引数宣言 ) { 定義内容 } 関数定義は常にトップレベルでのみ行うことができます。引数の宣言の方式 は、基本的に ANSI 形式になっています。K&R 形式は使用できません。しか し引数の型チェックが行われるわけではありません。また引数無しの宣言時 に voidを記述することができません。宣言した引数のスコープは、その定 義した関数の内部です。関数の戻り値の型を省略すると int です。 <例> int main( int argc, char **argv ) { ... } ●制御文 if( 式 ) 実行文 if( 式 ) 実行文 else 実行文 while( 式 ) 実行文 do 実行文 while( 式 ); for( 式 ; 式 ; 式 ) 実行文 (for内の式はどれも省略可能) switch( 式 ) 実行文 ラベル: 実行文 case 式 : 実行文 default : 実行文 goto ラベル; break; continue; return 式 ; (式は省略可能) 各実行文は、単一の行、もしくはブロックです。ブロックは { 〜 } で囲ま れたもので、複数の行、またはブロックを含むことができます。auto 変数 の定義は、各ブロックの先頭でのみ行えます。 ●特殊構文 asm( 文字列定数 ); 文字列定数の内容をそのまま(通常はアセンブラへ)出力します。こ の行は、トップレベルに記述しても関数定義内部に記述しても構い ません。ここで記述する文字列定数は、C言語のエスケープキャラ クタを含ませることはできますが、文字列定数の連結はできません。 asm( : 式 ); 式の値を評価します。評価した結果は特定のレジスタ(CPUによって 異なる)に入るため、その後の asm( 文字列 ) 文で式の結果を活用 できます。 コンパイラは asm() 文中でのスタックの変化を知りません。そのため、ス タックを変化させたまま asm() 文を終了してしまうと、コンパイラの内部 情報とコードとの不整合が起こります。スタックを扱う場合は十分注意して 下さい。 ●プリプロセッサ プリプロセッサは他のCコンパイラのものをそのまま使用します。そのため、 プリプロセッサの機能自体は使用するプリプロセッサに依存します。プリプ ロセッサの機能には次のものがあります。 ・/*〜*/のコメント削除 ・#include によるファイル連結 ・#define によるマクロ定義と置換 ・#ifdef/#ifndef/#if〜#elif〜#else〜#endif による条件付コンパイル ・行末の \ による行連結 プリプロセッサから渡される情報行として「# 行番号 "ファイル名"」の形 式を、現在のファイル上の位置として受け取ることができます。 ●多次元配列について 多次元配列は、1次元配列でそのまま実現可能です。例えば int で 5x10 の 配列が必要な場合 通常のC 1次元で表現 -------------------------------------------------------------------- int a[5][10]; int a[5*10]; b= a[1][2]; b= a[1*10+2]; return a[i][j]; return a[i*10+j]; この両者は、プログラムで行っている意味も、また処理の効率も全く同一で す。 『その他の注意点』 ●最適化 現在ターゲットが68000の場合はオプティマイザが存在していないため最適 化はほとんどかかりません。特に CPU に依存する命令展開の最適化は皆無 です。コンパイラが展開時に行っている最適化は以下の点だけです。 ・定数計算の丸め込み1 int a= 10+30; → int a= 40; ・定数計算の丸め込み2 return a[0]; → return *a; ・意味のない式計算の除去 for( 10*a ;〜 → for(; 〜 "abcdef"; → 参照されない文字列定数は展開しない ・副作用のみ必要な式において、返り値の展開を省く a && b; → if( a ) b; 結果として 0 または 1 を得るコードがなくなり、if 文と 完全に同じコードになる。 また、そのターゲットCPUでコードジェネレータが対応している命令レベル によっても、コードの質が大きくかわってきます。 ●現在まだ未確定の仕様と対処予定のバグ ・まだ動作チェック段階であるため、各種ワークバッファが現在固定サイズ で確保されています。 ・配列の初期化で初期値リストが与えられている場合、添え字の要素数が無 視されます。 <例1> static char a[10]= { 1, 2, 3 }; この場合実際は 3byte しか確保されない <例2> static char a[10]= "abcdefg"; この場合実際は 8byte しか確保されない ・変数代入やポインタ参照における型のエラーチェックがほとんど(全く)行 われません。 ・scoot でルールセットの定義ファイルを与えることができません。 ・日本語に一切対応していません。特に漢字コード中にエスケープ文字があ ると動作が不定になります。 ・goto で後ろのラベルへジャンプできません。 ・関数のプロトタイプ宣言は、関数定義内部で行う場合は必ず static か extern の記憶クラス指定が必要です。グローバルで宣言する場合は不用。 ●利用制限 必ず readme.doc を参照したのち、その利用制限に従って下さい。 ●参考文献 (1)「lex & yacc プログラミング」John R.Levine/Tony Mason/Doug Brown 共著, 村上 列 訳,アスキー出版 (2)「UNIXプログラミング実践編」金崎克己著,CQ出版社 -- 小笠原博之 oga@art.udn.ne.jp http://www.vector.co.jp/authors/VA004474