else - みる会図書館


検索対象: 月刊 C MAGAZINE 1992年9月号
19件見つかりました。

1. 月刊 C MAGAZINE 1992年9月号

List3 ASM OUTPUT DOUBLE OPERAND ASM OUTPUT FLOAT OPERAND アセンプラの命令に double , float のデー タオペランドが現れる場合の出力方法を 定義しておくマクロて、す。オリジナルは GAS (GNU AssembIer) シンタックスに なっています。 これて、 tm. h の作成は終わりました。 GCC Ver. 2.1 て、は GCC Ver. 1 .40 において関数 て、なくマクロて、あったものが相当な数関数 として記述されているのて、 , ファイルサイ ズが小さくなっています。 md の変更 md は GCC の内部形式て、ある RTL を , ター ゲット CPU のアセンプラに変換するための 機種依存ファイルて、す。 X68000 て、 GCC を動 かすためには , md の中に記述されている命 令をいくつかアセンプラの記述仕様の違い て、 , 変更を加えなければなりません。 この変更には 2 種類あって , ひとつは本来 の目的て、ある記述仕様違いによる変更と , CPU が 68000 て、あるため , いくつかのコード シーケンスを、、 68000 て、はこっちが速い〃命令 に置き換えを行います。前者は必須の変更 て、 , 後者は GCC を動かすためにだけなら余 計ともいえる変更て、す。 必須の変更項目としてはいくっかの命令 に現れるイミーディエイトオペランドが C 言 語ライクな、、 # 0xFFFF クのような記述になっ ているものを、、 #$ffff" に置き換えを行うもの や , ジャンプ命令、、 jbra 〃を、、 bra クに置き換え を行うものがあります。 また , 速い命令への置き換えは 68000 て、は clr 命令をとくに注意して置き換えます。オ ペランドがデータレジスタの場合には clr. 1 d? 命令よりは moveq. 1 # 0 , d ? 命令のほうが速い のて、こちらを使うように改めることを慎重 に行います。このほかにも独自の覗き穴最 適化を定義 , 追加をしています。 #else #endif else if (GET_CODE (operands[0]) ニ MEM & & GET_CODE (XEXP (operands [ の , の ) = PRE_DEC) return \ ” moveX. w % 1 , % 0 \ ; cIrX. w % 0 \ ”・ else if (GET—CODE (operandsC0]) = MEM & & GET_CODE (XEXP (operands [ 0 ] , の ) = POST_INC) return \ ” clr %. w X0%;moveX. w % 1 , % 0 \ ”・ else return }"cIrX. 1 % 0 \ ;moveX. w % 1 , % 0 \ " ・ if (DATA_REG_P (operands [ 0 ] ) ) if (GET_CODE (operands[l]) & & REGNO (operands [ 0 ] ) #ifdef HUMAN68K_ASM return \ ” andX. 1 X#$ff, % 0 \ ”・ #else return }"and%. 1 %#0xFF, % 0 \ " ・ #endif if (reg-mentioned-p (operandsC0], operandsC1])) #ifdef HUMAN68K_ASM return \ ” moveX. b % 1 , % 0 \ ; andX. 1 X#$ff, % 0 \ " ・ #else return \ ” move%. b % 1 , XOY;andX. 1 X#0xFF, % 0 \ ”・ #endif #ifdef GCC_RUNS_X68 return TARGET 68020 ? #"clr%. 1 X0};move%. b % 1 , % 0 \ ” : #"moveqX. 1 % # 0 , % 0 \ ; move%. b % 1 , % 0 \ ”・ return \ ” clr %. 1 XOY;move%. b % 1 , % 0 \ ”・ output-asm-insn ( \ ” clr %. w % 0 \ ” , operands) ; operands [ 0 ] = adj-offsettable—operand (operands [ 0 ] , 2 ) ; return }"moveX. w % 1 , % 0 \ ” (define insn [(set (match—operand: SI 0 "general—operand" ” = do く〉 , d ” ) (zero—extend: SI (match—operand :QI 1 "nonimmediate_operand ” = REG = REGNO (operandsC1])) #else #endif else if (GET-CODE (operandsC0]) ニ MEM & & GET_CODE (XEXP (operands [ 0 ] , の ) ー = PRE_DEC) operandsC0] = XEXP (XEXP (operandsC0], の , の ; #ifdef MOTOROLA #if defined (SGS) Ⅱ defined (HUMAN68K_ASM) return \ ” clr %. 1 -(% の };moveX. b % 1 , 3 ( % 0 ) \ ” ; #else return \ ” clr %. 1 ー(% の };move%. b % 1 , ( 3 , % の \ " ・ #endif #else #endif else if (GET_CODE (operandsC0]) ニ MEM の , の ; operandsC0] = XEXP (XEXP (operandsC0], #ifdef MOTOROLA #if defined (SGS) Ⅱ defined (HUMAN68K-ASM) return #"clrX. 1 (% の + %;moveX. b % 1 , ー 1 ( % の \ " ・ #else return *"clrx. 1 (% の + };moveX. b % 1 , ( ー 1 , % の \ " ・ #endif return \ ” clrl X0@-#;moveb % 1 , % 0@( 3 ) \ ”・ & & GET_CODE (XEXP (operands [ 0 ] , の ) ー = POST_INC) 144 C MAGAZINE 1992 9

2. 月刊 C MAGAZINE 1992年9月号

List 3 #else #endif else return \ ” clrl %0@+ #;moveb % 1 , % 0@( ー 1 ) \ " ; output_asm—insn (}"clrX. 1 % 0 \ ” , operands) ; operands [ 0 ] = adj—offsettable—operand (operandsC0] BasiC conditional jump instructions. return *"moveX. b % 1 , % 0 \ " ・ (define insn "beq' [(set (pc) (if_then-else ()q (cc0) (const_int の ) (label_ref (match-operand #ifdef HUMAN68K_ASM #ifdef MOTOROLA 0 OUTPUT_JUMP (\ ” beq % 1 時” , }"fbeq % 10 \ " , }"beq % 10 \ " ) ; OUTPUT_JUMP (}"jeq % 10 \ " , }"fjeq % 10 \ " , }"jeq % 10 \ " ) ; #endif #else #endif #else OUTPUT JUMP (\ ” jbeq % 10 \ " ほかには , おそらくバグだと思われます が , 出力する命令が複数になる場合 , Hum an68k のアセンプラて、は許されないマルチス テートメント形式て、 ( 、、 ; 〃て、いくっかの命令 を同一行に記述する ) 命令が出力されること があるのを直します。これは GCC Ver. 1. 40 の 68000 系の md にはなかった追加された部 分に集中しているのて、 , おそらく Ver. 2.1 に変更した際のエンバグと思われます。 List 3 は X68000 版 md の抜粋て、す。この m d ファイルて、はマクロ GCC RUNS X68 とマ クロ HUMAN68K ASM を使い分けて変更 を加えてあります。 GCC RUNS X68 て、有 効になる変更は「オリジナルのままて、も問題 ない」変更 , HUMAN68K ASM て、有効にな るのは必須の変更て、す。一部のパソコン通 信に流した GCC Ver. 2.1 の X68000 版への差 分て、この GCC RUNS X68 の使い方がまず いのて、は ? との指摘がありましたのて、 , 将来変更を行うつもりて、います。 aUX-OUtput. c の変更 こちらも , アセンプラと RTL とのインタ フェイスを行うファイルて、すが , GCC Ver. 1.40 て、はマクロて、あったものがこのファイ ルに関数として記述されています。こちら はすべてアセンプラ仕様の違いを吸収する ための変更て、す。ただ , X68000 て、は先ほど の md についてもいえますが , 浮動小数点コ プロセッサ命令については標準となるアセ ンプラ記述仕様がありません。 一部のヘビーユーザの方はフリーソフト ウェア fppp. x を用いて GCC に一 m68881 オプシ ョンを指定してこの浮動小数点命令を使う 場合があるようて、すが , 今の時点て、は GCC が出力する浮動小数点コプロセッサ命令は X68000 て、の標準というわけて、はありません。 X 68 k 活用講座 top 厄 v. c の変更 toplev. c は , コンパイラ本体の main( ) 関 数が記述されたソースファイルて、す。少し 前まて、はフリーソフトウェア「とえんていわ ん」 ( 川本琢二氏作成 ) がなかったのて、 , rtl を ダンプするファイルのファイルネームが Hu man68k て、は作成て、きない名前て、あったりし たために , rtl ダンプ関係の処理をすべて削 ったりしたのて、すが , 現在のバージョンて、 はオリジナルのままとくに変更は加えてい ません。 Human68k にないシグナル関係の 処理をソースから外すように変更するのと , コンパイラが時間計測に用いている関数の 部分を IOCS ONTIME て、時間を得るように 変更を加えるだけて、す。 実行ファイルの作成 ソースファイルの用意がて、きたら , 後は make を実行するだけて、す。 X68000W て、 G CCVer. 1.40 て、コンパイルを行って約 2 時間 て、実行ファイルが生成されます。 Fig. 1 は X 68000 版 GCC Ver. 2.1 の作成に使っている ディレクトリに存在するソース一覧て、す。 GCC Ver. 1 . 40 て、のコンパイルにはフリー メモリが約 5M バイト必要て、す。フリーメモ リ 4M バイトて、は一部のソースがメモリ不足 て、コンパイルて、きません。 この報告て、は触れませんて、したが , GCC Ver. 2.1 はシフト JIS 漢字が含まれたソース を正しく処理て、きるようになっています。 終わりに 原稿執筆時点て、 , GCC Ver. 2.1 て、 GCC V er. 2.1 を作成可能になっています。時間の 都合て今回のレポートて、は GCCVer. 2.1 て、 の新しい機能や最適化などについて追及が て、きませんて、した。これらについては機会 を改めて詳しく報告したいと思います。 X68k 活用講座 145

3. 月刊 C MAGAZINE 1992年9月号

電卓プログラム ( 第 2 版 ) List 5 1 : #include く stdio. h> 2 : #include く stdlib. h> 3 : #include く th. h> 4 : #include く ctype. h> 5 : 6 : enum (C—NUMBER, C—ADDOP, C—MULOP, C LPAREN CZOTHERS) ; C RPAREN, C_NEWLINE, C_EOF, 7 : 8 : 9 : int C operator ; 10 : i nt token ; 11 : double V41ue; 12 : int errors ; 13 : 14 : double term(void) : 15 : double factor(void) : 16 : 17 : void get—number(void) 18 : { char buffC30] ; 19 : int p = 0 ; 20 : do { 21 : buff[p + + ] = c; 22 : c ニ getchar() ; 23 : } while (isdigit(c)); 24 : 25 : buffCp + + ] ニ c; 26 : c ニ getchar() ; 27 : while (isdigit(c)) 28 : buffCp + + ] = c; 29 : c = getchar() : 30 : 31 : 32 : 33 : 34 : 36 : 37 : void get—token(void) while (c 39 : c ニ getchar() ; 40 : switch (c) { 41 : case ' 0 ' : case ' 1 ' : case ' 2 ' : case ' 3 ' : case ' 4 ' 42 : case ' 5 ' : case ' 6 ' : case ' 7 ' : case ' 8 ' : case ' 9 ' get-number() ; 44 : / * get-number ( ) の中で先読みして * / 45 : token = C NUMBER; 46 : / * いるので , そのまま return する * / return ; case ' 十 : case 48 : operator 49 : token = C_ADDOP; 50 : break ; case ' * ' : case ' / ' 52 : 01mator 53 : token ニ C MULOP; 54 : break ; case ' ( ' ・ 55 : token = C LPAREN ; 56 : break ; case ' ) 58 : token ニ C RPAREN ; 59 : 60 : break; case ' 61 : token ニ C NEWLINE;. 62 : 63 : 64 : return; case EOF: 66 : token return, 68 : default: 69 : tOken : C OTHERS; break; 71 : 72 : c ニ getchar(); 73 : return; 74 : } 75 : 76 : void error(void) wh i le (c ! ニ ' yn' & & c ニ EOF) c = getchar(); printf("?#n") ; List 5 token ニ (c errors ' *n' ) ? C_NEWLINE : C_EOF; 81 : 82 : 83 : 84 : } 85 : 86 : double expression(void) 87 : { 88 : int op; 89 : double u, v; 90 : u = term(); 91 : while (token ニ C_ADDOP) { 92 : 93 : OP ニ tor ; ・ 00 ー、 0 、 00 ( ) : 94 : v = term() : 95 : if (op 96 : 98 : else 99 : 100 : 101 : 102 : } 103 : 104 : double term(void) 105 ・ 106 : int op; 107 : double u, v; 108 : u = factor() ; 109 : wh i le ( token ニ C_MULOP) { 110 : 111 : / * オペレータを保存 * / OP = operator; get-token ( ) ; 112 : " / " を読みとばす * / v ニ factor() ; 113 : if (op 114 : 115 : 116 : else 117 : 118 : 119 : 120 : } 121 : 122 : double factor(void) 123 : 124 : double v; switch (token) { 125 : / * 左括弧の場合 * / 126 : case C LPAREN: get_token() ; / * " ( " を読みとばす * / 127 : V = expression() ; 「式」の処理 * / 128 : i f ( token = C RPAREN) / * " ) " が来ているはす。チェック * / 129 : get—token ( ) : 130 : / * " ) " を読みとばす * / 131 : else error() ; 132 : 133 : break ; 134 : case C NUMBER : 135 : V = value; ge t—token ( ) ; 136 : 137 : break ; 138 : default: error() ; 139 : 140 : 141 : return v; 142 : } 143 : 144 : void one_line(void) 145 : 146 : double v; 147 : errors V ニ expression(); 148 : if (token ! ニ C_NEWLINE) 149 : error() : 150 : if (errors 151 : printf("XfYn", v) ; / * 結果の表示 * / 152 : get—token ( ) ; 153 : / * 改行の読みとばし * / 154 ・ 155 : 156 : void main(void) 157. get-token ( ) ; 158 : while (token ! ニ C_EOF) 159 : one-l ine() ; 160 : 161 : / * オペレータを保存 * / / * ” + " を読みとばす * / / * 因子 * / return u : buffCp) ニ value = atof(buff) ; return u ; / * 因子 * / / * 数値の場合 * / / * 数値の処理をする * / / * 数値を読みとばす * / / * " ( " でも数値でもなければ , 工ラー * / / * 先読みしない * / / * 先読みしない * / : C_EOF; / * 先読み * / / * 先読み * / 66 C MAGAZINE 1992 9

4. 月刊 C MAGAZINE 1992年9月号

X 68 k 活用講座 varasm. c て、使用される GCC を拡張して利 用するために用意されているマクロて、す。 私が新しく作ったものて、はありません。 GCC には随所にこのような拡張を施すた めのマクロが用意されていますが , オリ ジナルの GCC て、はこのマクロは #define さ れていません。これも SX-Window プログ ラムをサポートするためのマクロて、 , ア センプラのセクションを増やしてありま す。 今回 , テパッグにおいて追加されたマクロ List 1 : #define ASM_OUTPUT_DOUBLE_OPERAND(FILE, VALUE) 2 : do { union { double d; long 1 [ 2 ] ; } tem; tem. d ニ (VALUE) ; 3 : fprintf (FILE, ” # $ % 08X , # $ % 08X " , tem. 1 [ 0 ] , tem. 1 [ 1 ] ) ; 4 : } while ( の 5 : 6 : 7 : #define ASM_OUTPUT_FLOAT_OPERAND(FILE, VALUE) 8 : do { union { float f; long 1 ; ) tem; = (VALUE) ; 9 : tem. f fprintf (FILE, 10 : ” # $ % 08X " tem. 1 ) ; } while ( の 11 : 囎 A SECTION FUNCTIONS これも EXTRA SECTIONS マクロと同様 に拡張用に用意されたマクロて、す。 X680 00 版 GCC て、はたくさんのセクションがあ るために、、 \ 〃て、継続された非常に巨大なマ クロになっています。ここて、反則と思わ れるのは , このマクロの継続て、次に説明 する SELECT SECTION マクロて、呼び出 される関数もあわせて定義している点て、 す。 SELECT SECTION 与えられた構文木からその構文木に適切 なセクションのアセンプラ疑似命令を生 成させるためのマクロて、す。これもオリ ジナル GCC て、は未使用の拡張用マクロて、 す。 ASM OUTPUT ASCII 文字列リテラルをアセンプラの命令に展 開するためのマクロて、す。 GCC 移植の先 駆者近藤真己氏のマクロを元に , 非漢字 バイナリ文字列 ( 工スケープシーケンスて、 バイナリデータを文字列として定義した ものを勝手に命名しました。ゲームなど のソースにおいて暗号化データにときど きみられる手法て、す ) を正しく出力て、きる ようにパッチしたものて、す。 以上が 7 月号の tm. h に記述されたマクロの 説明て、す。 List 2 がその後デバッグにおいて 追加されたマクロて、す。 X68000 版 md の抜粋 List move instructions ; A special case in which it is not desirable tO reload the constant intO a data register. (define—insn C(set (match_operand: SI 0 ” push_operand" (match—operand: SI 1 ” general—operand ”” J ” ) ) ] ” GET_CODE (operands [ 1 ] ) = CONST INT & & INTVAL (operandsC1]) 〉 = ー 0X8000 & & INTVAL (operands[l]) く 0X8 圓 0 " #ifdef GCC_RUNS_X68 if (operandsC1] = const0—rtx & & TARGET_6802 の #else if (operands [ 1 ] constO_rtx) #endif return \ ” clr %. 1 % 0 \ ” return }"pea XaI#" ・ Patterns tO recogn ize zero—extend insns produced by the combiner. ・ We don' t a110W both operands in memory, because of aliasing problems. (define—insn C(set (match_operand: SI 0 "general_operand" " = do く〉 , d く ") (zero—extend : SI (match_operand :HI 1 "non immediate_operand" if (DATA_REG_P (operands[0]) ) if (GET_CODE (operands [ 1 ] ) & & REGNO (operands [ 0 ] ) #ifdef HUMAN68K_ASM return }"andX. 1 %#$ffff, % 0 \ " ・ #else return Y"andX. 1 X#0xFFFF, % 0 \ " ・ #endif if (reg_mentioned—p (operands[0], operandsC1])) #ifdef HUMAN68K_ASM return *"moveX. w % 1 , X0};and%. 1 X#$ffff, % 0 \ " ・ #else return }"move%. w % 1 , % 0 \ ; andX. 1 X#0xFFFF, % 0 \ " ・ #end if #ifdef GCC_RUNS_X68 return TARGET_68020 ? }"clrX. 1 X0#;moveX. w % 1 , % 時 " :}"moveqX. 1 % # 0 , X0};moveX. w % 1 , % 時 " ・ ニ REG = REGNO (operands[l]) ) X68k 活用講座 143

5. 月刊 C MAGAZINE 1992年9月号

関数名と変数名 TeX を作った大計算機科学者クヌースは , 名前について次のようなコツを紹介してい ます。「動詞は手続きにつける名前であり , データにつける名前ではない」これは , 手続 き ( C て、いえば関数 ) には動詞の名前を使い データ ( 変数や定数 ) には名詞を使いなさい ということだと思います。 LiSt 1 : 2 : 3 : 5 : 6 : 7 : 8 : 12 : 13 : 14 : { 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : } 26 : 28 : 29 : { 30 : 31 : 32 : 33 : 34 : 35 : 36 : 38 : 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46 : 48 : 49 : 50 : 52 : 54 : 56 : 58 : 59 : 60 : 63 : 64 : 66 : 68 : 69 : 70 : 72 : } 関数分けを行った例 ( find3. c ) #include く stdio. h> #include く process. h 〉 #include く string. h> 4 : #include く jctype. h> VOid int int 9 : #define NOTFOUND 10 : #define FOUND 11 : #define ERROR VOid main(int argc, char *argv[]) : search—file(char *Str, int len, char *fname) ; search—l ine(char *Str, int len, char *buffer) : 0 1 main(int argc, char *argv[]) int len, ac; 関数名 ・動詞 変数・定数名・・・名詞 たとえば , 「文字列を検索する」という関 数に keyword という名前をつけるのはよく ありません。 keyword は名詞だからて、す。 逆に「検索する文字列」を保持している変数 に search という名前をつけるのはよくあり ません。 search は動詞だからて、す。 search という名前は関数名に使い , keyword とい う名前は変数や定数の名前に使うのがよい のて、す。 クヌースは名前づけの真理として「言葉の 中にその機能を示唆するような工夫が必要 である」と述べています。動詞や名詞という 品詞の情報もないがしろにせず , 名前づけ に利用するべきなのてす。 クヌース先生のお話は , 有澤誠訳「クヌ ース先生のドキュメント纂法』 ( 共立出版 ) 関を分ける理由 を引用させていただきました。 るほど , 全体の処理を把握するのはそれだ ります。て、すからひとつの関数が太れば太 間が一度に考慮て、きる情報にはかぎりがあ ことがて、きるて、しよう。しかし実際には人 ば , どんなに複雑なプログラムも理解する もしも人間に無限の記憶能力があるなら があるからて、す。 も大きな理由は , 人間の情報能力にかぎり うか。いろいろ理由がありますが , もっと ところて、「関数に分ける」のはなぜて、しょ if (argc く 3 ) { fprintf(stderr, exit(-l); len = strlen(argv[l]); for ()c = 2 : ac く argc; ac + + ) { search—file(argv[l], len, argv[ac]) ; ” Usage: find3 string filename. 27 : / * 文字列を一つのファイルから検索し、表示する * / , int search—file(char *Str, int len, char *fname) while (fgets(buffer, 1024 , (p)) { lineno = IL; else { result = ERROR; fpr intf (s tderr, ” Xs cannot open*n" if ((fp : fopen(fname, "r")) = NULL) { resu 1 t = NOTFOUND ; int Char buffer[1024] ; long lineno; *fp; FILE fname) ; if (search—line(str, len, buffer) = FOUND) { printf("Xs (XId) : Xs", fname, lineno, buffer) : result = FOUND; lineno 十十 ; fclose(fp) : return(result) ; 53 : / * 文字列を一行から検索し、表示する * / int search_l ine(char *Str, int len, char *buffer) int int for (i = 0 ; bufferCi] ! = ' \ 0 ' ・ cont i nue ; inkanji if (inkanji) ( inkanji ニ 0 : else if (iskanji(bufferCi])) ( inkanji if (strncmp(&bufferCi], str, len) return(FOUND) : return(NOTFOUND) ; = の { 110 C MAGAZINE 1992 9

6. 月刊 C MAGAZINE 1992年9月号

e List 28 : } LiSt 単純な文字例検索 (findl. c) 1 : 2 : 3 : 4 : 5 : 7 : 8 : 9 : 10 : 11 : 12 : 13 : 14 : 15 : 16 : 17 : 18 : 20 : 22 : 23 : 24 : 25 : 26 : #include く stdio. h> #include く string. h> #include く process. h> void main(int argc, char *argv ロ ) ; void main(int argc, char *argv[]) len, i : int buffer[1024] ; Char FILE *fp; if (argc ! = 3 ) { printf( ” Found:Xs ” , buffer) ; if (strncmp(&bufferCi], argvC1], len) for (i = 0 ; buffer[i] ! = ' \ 0 ' ・ while (fgets(buffer, 1024 , (p)) { len = strlen(argvCl]); exit(-l); fpr intf (stderr, ” Xs cannot 0 n \ n ” , argv [ 1 ] ) ; else if ((fp = fopen(argvC2], exit(-l); fprintf(stderr, ” Usage: findl string filename%n fclose(fp) ; 単純な文字例検索の改良 (find2. c) 2 : 3 : 4 : 5 : 6 : 8 : 9 : 10 : 11 : 12 : 13 : 14 : 15 : 17 : 18 : 19 : 20 : 22 : 23 : 24 : 25 : 26 : 27 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 37 : 38 : 39 : 40 : 1 : #include く stdi0. h> #include く process. h> #include く string. h> #include く jctype. h> void main(int argc, void main(int argc, long int Char FILE 1 i neno ; len, i, char *argv [ ] ) ; char *argv ロ ) ac, inkanji buffer[1024] ; *fp; if (argc く 3 ) { fprintf(stderr, exit(-l); len = strlen(argvC1]); fO 「 ()c = 2 : ac く argc, ” Usage: find2 string filename. if ((fp = fopen(argv[ac], ” (")) = NULL) { fpr intf ( s tderr, "XS cannot open*n" , argv CacJ ) ; SSe111Ce 0 た。しかし , 実は間題はこれからて、す。プ 目はすべてアルファベットになります。っ イトになりますが , その 2 番目 , 4 番目 , 6 番 とえば , 「テスト」という文字列は 16 進て、 6 バ や記号がくることがありうるからて、す。た JIS の第 2 バイト目に普通のアルファベット ルは正しく検索て、きません。これはシフト List 1 の検索て、は漢字を含んて、いるファイ と迷います。 か。 fclose の位置はここて、いいのか。ちょっ を開始しないための continue はどこに飛ぶの どこて、増やすのか。漢字 2 バイト目から検索 行番号を示す変数 lineno はどこて、初期化し , によるミスが多くなってきます。たとえば , た印象を与えます。複雑になると , 勘違い また for 文があるのがどうもごちやごちゃし の中に while 文があり , その while 文の中に かなり複雑になりましたね。とくに for 文 てみましよう (List 2 ) 。 上の四つの要求を入れたプログラムを作っ だんだんだんだん複雑になっていきます。 うな改良要求にひとつひとっ答えていくと , 最初は単純だったプログラムも , このよ ・漢字も正しく検索したい ・ファイル名を表示したい ・複数ファイルから検索したい ・見つかった行の行番号を表示したい 要求がすぐに出てくるて、しよう。 機能しか持っていませんから , 次のような この文字列検索プログラムはとても単純な いろいろと改良したくなってくるものて、す。 ログラムというものは , 使っているうちに continue; lineno ニ IL; while (fgets(buffer, 1024 , (p)) ( if (inkanji) { inkanji ニ 0 : inue ; for (i = 0 ; buffer[i] ! = ' 判 ' : i + + ) { fclose(fp) : lineno 十十 ; printf("Xs (Xld) : xs"' if (strncmp(&buffer[i], argv[l]' len) inkanji ニ 1 : else if (iskanji(buffer[i])) { = の { 1 ineno, ス ・ 58 ト 83 65 83 83 buffer) : となっているのてす。 findl. c て、は漢字を考 慮していないのて、 , 、、 e 〃 という文字列を探 プログラミングの工ッセンス 107

7. 月刊 C MAGAZINE 1992年9月号

実践アルゴリズム戦路解法のテクニック 構文解析」を取りあげ , 簡単な関数電卓を作 成しました。このプログラムはまだまだ拡 張の余地があります。たとえば , 下記のよ うな機能拡張が考えられます。 ・関数や演算子の追加 ・変数の機能 ・条件判定 / ループ ・配列 19 : { 32 : } token ニ C MULOP; 78 : { 99 : } token ニ C_EOF ; これらの拡張を行えば , BASIC のインタ 電卓プログラム ( 第 3 版 ) プリタのようなプログラムを作れることに なります。興味のある方はどんどんチャレ ンジしてください。しかしながら , この稿 て、述べた技術だけて、は , 本格的なコンパイ ラを記述するには明らかに不十分て、す。よ り進んだ勉強のためには , 後述の参考文献 を参照されることをお勧めします。 再帰の説明は取りあえず一段落というこ とにして , 次回からは「分割統治法」による List 8 アルゴリズム設計法について解説します。 [ 参考文献 ] [ 1 ] 工イホ , セシィ , ウルマン共著 , 原田 訳 , 「コンバイラ I ・Ⅱ』 , サイエンス 社 [ 2 ] 佐々正孝著 , 『プログラム言語処理 系』 , 岩波書店 [ 3 ] 中田育男著 , 「コンパイラ』 , 産業図書 2 : 3 : 4 : 6 : 8 : 9 : 10 : 11 : 13 : 18 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 28 : 29 : 30 : 31 : 33 : 36 : 38 : 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46 : 48 : 49 : 50 : 51 : 52 : 53 : 54 : 55 : 56 : 57 : 58 : 59 : 60 : 61 : 62 : 63 : 64 : List 8 1 : #include く stdi0. h> #include く stdlib. h> #include く string. h> #include く math. h> 5 : #include く ctype. h> 7 : enum {C-NUMBER, C- ADDOP, C-MULOP, C LPAREN, C RPAREN, 66 : 68 : 69 : 71 : return ; case EOF: return ; default: break; token ニ C_OTHERS ; C_SIN, C_SQRT, C NEWLINE, C EOF, C_OTHERS } ; int C int token; operator ; 73 : 74 : 76 : 81 : 82 : 83 : 84 : 85 : 86 : 88 : 89 : 90 : 91 : 92 : 93 : 94 : 95 : 96 : 97 : 98 : 100 : 102. 103 : 104 : 105 : 106 : 107 : 108 : 109 : 110 : 111 : 112 : 113 : 114 : 115 : 116 : 117 : 118 : 119 : 120 : 121 : 122 : 123 : 124 : 125 : 126 : 127 : ] 28 : 77 : double expression(void) c = getchar() ; return ; 12 : double value; int errors; 15 : double term(void); 16 : double factor(void); int get_ident(void) char buff[30] ; int p ニ 0 ; do { buff[p] ニ ' \ 0 ' ・ } while (isalnum(c)); c = getchar() ; buff[p + + ] = c; return C OTHERS ; return C_SQRT; if (strcmp(buff, "sqrt ” ) return C SIN ; if (strcmp(buff, "sin ” ) / * 因子 * / case case int sign, op; double u, v; sign ニ 1 ; if (token = C_ADDOP) { if (operator sign ニー 1 : get—token() : u = sign * term(); whi le (token ー = C-ADDOP) { OP = operator; get-token ( ) ; v = term(); return u; else if (OP 34 : void get-token(void) while (c c = getchar() ; if (isalpha(c)) { token ニ get—ident() ; return ; switch (c) { case ' 0 ' : case ' 1 ' : case case ' 5 ' : case ' 6 ' : case get—numtm() ; token ニ C NUMBER; return ; case ' 十 ' : case 0EErator ニ C ; token ニ C_ADDOP ; break ; case ' * ' : case ' / 101 : double factor(void) case case operator break ; case ' ) ' break ; case ' ( ' ・ break ; token ニ C NEWLINE: token = C RPAREN ; token ニ C LPAREN ; double v; switch (token) { case C_LPAREN : get—token() : V ニ expression() : i f ( token ニ C RPAREN) get—token(j; else error ( ) ; break ; case C NUMBER: V ニ value; get—token ( ) ; break ; case C SIN: get-token ( ) ; v ニ sin(factor()) : break ; case C_SQRT : get—token() : v ニ sqrt(factor()) ; break ; default: / * オペレータを保存 * / / * " + ”を読みとばす * / / * sqrt( 因子 ) * / / * ” sqrt ”を読みとばす * / / * ” sqrt ”の場合 * / / * sin( 因子 ) * / / * " sin ”を読みとばす * / / * ” sin ”の場合 * / / * 数値を読みとばす * / / * 数値の処理をする * / / * 数値の場合 * / / * " ) " を読みとばす * / / * ” ) " が来ているはず。チェック * / 「式」の処理 * / / * " ( " を読みとばす * / / * 左括弧の場合 * / / * 因子 * / error() ; return V : / * ” ( ”でも数値でもなければ , 工ラー * / 129 : } 68 C MAGAZINE 1 2 9

8. 月刊 C MAGAZINE 1992年9月号

の処理内容て、す。前述のように , 式は数値 句を扱うために get token ( ) という関数を作 す。 と演算子が交互に現れます。コンパイラの こて、作成する get_token ( ) は , 成します。 字句の表現の仕方にもいろいろな流儀が 呼び出しのたびに数字または演算子の「字句」 用語て、は , これら数値や演算子のことを tok あり , 1 文字て、表現することもあります ( 数 en ( トークン ) とか字句と呼びます。この字 を大域変数 token にセットするようにしま 字は、、 0 〃て、代表させる ) が , こて、は字句を プログラムの概要 List 3 List 1 get—number() ; token ニ C_NUMBER; return; case 十 : case operator token = C_OP; c ニ getchar() ; return ; token = C_NEWLINE; return; case EOF: token = C EOF; return ; default : token ニ C_OTHERS; c = getchar() ; return; 39 : 40 : 41 : 42 : 43 : 44 : 45 : 46 : 48 : 49 : 50 : 51 : 52 : 53 : 54 : 55 : 56 : 57 : 58 : 59 : } 61 : void error(void) 62 : { while (c ! ニ '}n' & & c ! ニ EOF) 63 : c = getchar() ; 64 : printf("?}n") ; (c ' yn' ) ? C_NEWLINE 66 : token : C EOF ; 67 : 68 : } 69 : 70 : void one line(void) 71 . int op; 73 : double v; get-token() ; if (token = C_EOF ) 76 : return; else if (token = C_NUMBER) 77 : value; 78 : V else { 79 : error() ; 80 : 81 : return; 82 : 83 : 84 : 85 : 86 : 88 : 89 : 90 : 91 : 92 : 93 : 94 : 96 ・ 98 : 99 : 1 圓 : 101 : 102 : 103 : 104 : 105 : 106 : 107 : 108 : void main(void) 109 : { while (token ! = C_EOF) 110 : one line() ; 111 : 112 : 1 : main() do { 3 : 一行の処理 ( ) ; 4 : } while ( ファイルの終わりでない ) : 5 : case case 電卓第 1 版の疑似コード List 2 1 : 一行の処理 ( ) get—token() ; 3 : / * token は C_NUMBER か C—EOF であるはず * / 4 : if (token ー = C_EOF) 5 : return; 7 : get—token() ; / * 先読み * / 8 : while (token ニ C_OP) { 9 : / * 演算子を保存 * / 10 : OP ニ operator ; get_token() ; 11 : / * 演算子の後なので token は C ー NUMBER のはす * / 12 : 演算子ごとの処理 13 : get—token() ; / * 先読み * / 14 : 15 : / * token は C_NEWLINE のはず * / 16 : 17 : return; 18 : } 電卓プログラム ( 第 1 版 ) 1 : #include く stdio. h> 2 : #include く stdlib. h> 3 : #include く math. h> 4 : #include く ctype. h> 5 : 6 : enum {C-NUMBER, C_OP, C OTHERS}; C_NEWLINE, C EOF, 7 : 8 : int C operator ; 9 : int token; 10 : double value; 11 : 12 : void get number()o id) 13 : { char buffC30]; 14 : i nt p = 0 ; 15 : do { 16 : buff[p + + ] c = getchar() ; 18 : } while (isdigit(c)); 19 : if (c 20 : buff[p + + ] 21 : c = getchar() ; 22 : while (isdigit(c)) { 23 : buff[p + + ] 24 : c = getchar() ; 25 : 26 : 28 : 29 : 30 : } 31 : 32 : void get—token(void) 33 : { while (c 34 : c = getchar() : 35 : switch (c) { 36 : case ' 4 ' : case ' 0 ' : case ' 1 ' case case case ' 5 ' : case ' 6 ' 38 : case case List 3 1 get-token() ; / * 先読み * / whi le (token ニ C-OP) ( / * 演算子を保存 * / OP = operator; get token() ; if て token ! = C_NUMBER) { error() ; return; switch (OP) / * 演算子ごとの処理 * / + = value; break; V cas e 十 value; break; V * ニ value; break; V cas e * case ' / ' : V た value; break; get—token() : / * 先読み * / if (token ! = C_NEWLINE) ( error() : return ; printf( ” Xf*n ” , v) ; retu rn ; buffCp] value = atof(buff) ; / * 結果の表示 * / 1 的 2 9 62 C MAGAZINE

9. 月刊 C MAGAZINE 1992年9月号

ln a ⅱ行靏 m 町ⅱ Ma れ門 システム・ワン Power C Ver. 2. OJ Power Ctrace 数が呼び出している子の関数に移 この数字は , おのおのの文が実行 の情報の表示を切り換えます。 今回は , Power Ctrace の機能の 動します。 されるたびにひとつずっ増加しま 中て , トレースの軌跡を中心に説 実行された軌跡を このとき , 関数の移動にともな す。 明します。 逆方向にたどる Fig. 1 は , 途中まて、実行したとこ って変数ウインドウの情報も変化 実行の軌跡 Power Ctrace て、は 255 個の文ま しますのて、 , 関数の内部て、使用さ ろて、実行の軌跡を表示したものて、 て、逆方向にたどることがて、きます。 れている自動変数の値を確認する す。この例てはループの中て条件 逆方向トレースモードに入るため 場合などに威力を発揮します。 選択処理を行っていますが , どの Power Ctrace て、はプログラムの には , 国キーや国キーを使用しま 各文が何回実行されたかを表示す 部分がどれほど実行されているか トレースする関数の選択 す。国キーは一時カーソルを最大 ることがて、きます。各文の実行回 わかると思いますにこて、示してあ 255 個まて、逆方法に移動させます。 数を調べることにより , 負荷の分 る実行の回数は , 整数入力に対し 国キーは一時カーソルを実行され 通常て、は , / t オプションをつけて 布や処理の分岐状況が確認て、きま て , 正整数を五個 , 負整数をふた た最後の文の位置に移動させます。 コンパイルされたプログラム中の っ入力して , 最後に 0 を入力した場 す。 すべての関数がトレースの対象と 合の状況て、す ) 。 このとき , ステータスラインの実 Power Ctrace 起動時のソースウ もう一度国キーを押すと , ウィ なります。しかし , プログラム中 行カウンタ , 関数名 , 関数の深さ インドウの左側部分には数字が表 の部分が反転して , 逆方向トレー の全関数をトレースする必要がな 示されています。これは行番号て、 ンドウの左端部分の数字が消去さ スモードに入ったことを示します。 い場合もあるて、しよう。 れ , その分だけソースプログラム あり , プログラムを見やすくして 実行の軌跡をたどるには , 曰キ そうした場合には , 関数単位て、 の表示幅が増加します。ソースプ ーと曰キーを使用します。曰キ 国キーを押すと , ウインドウの トレースを ON / OFF するトレース ログラムをて、きるだけ多く表示さ コマンドを使用します。国キーを ーを押すたびに , 一時カーソルは せたい場合にはこのモードを使用 左端部分に実行の軌跡が表示され します。このように , キーはト ひとつ前の実行文に戻ります。ま 押すとポップアップウインドウが ます。実行の軌跡情報は実行可能 た , 曰キーを押すと一時カーソル 現れ , 現在トレース可能な関数の グル動作により上記に示した 3 種類 な文に対してのみ表示されます。 一覧が表示されます。 こて、 , ト は次に実行された文に移動します。 Fig. 1 実行の軌跡の表示例 レース制御をしたい関数シンボ、ル このようにして , 文がどのように の位置にカーソル移動して曰キー して実行されたかを遡って見るこ とがて、きます。 を押すと , トレースが OFF に設定 され , 国キーを押すとトレースが 逆方向トレースモードて、は , プ ON に設定されます。 ログラムを実際に実行しているわ ファイルの分割もトレース制御 けて、はないのて、 , 変数の値は変化 の方法のひとって、す。この場合 , しません。 ソースファイルを分割してトレー 関数呼び出しの軌跡 スしたい関数を含んて、いるソース ファイルだけを / t オプションをつけ 国キーと国キーを使用すれ てコンパイルし , それ以外のソー ば , 関数呼び出しの軌跡をたどる スファイルは / t オプションをつけな ことがてきます。このコマンドは いて、コンパイルします。 鎖状に呼び出されて , アクテイプ 後者の方法を用いると , デバッ になっている関数の軌跡をたどる グ関連の情報を含まない分だけメ モリを節約することがて、きます。 ために用いられます。 回キーを押すたびに , 現在表示 このため , チェックに関連のない 部分は外しておいたほうがよいて、 されている関数を呼び出した親の 関数に移動します。また , 国キー を押すと , 現在表示されている関 #include VOid く stdio. h> main( void ) int value int pos_count int neg_count 1 pos—count 1 neg count whiie ( 1 ) { 8 scanf( ” % d ” , &value ) ; 8 if ( value > 0 ) { 8 5 pos_count 十十 3 else if ( value く 0 2 neg_count 十十 else { break ; print{ "POSitive 1 s Xd. pos_count printf "Negative 1 s Xd. neg_coun t return in case Of in case Of negative in case Of zero 1 、 1 0 0 lnformation from Compiler Makers 159

10. 月刊 C MAGAZINE 1992年9月号

ドライプ 1 のトラック 1 , ヘッド 0 , セクタ 1 を読み込んで表示する ( FDD 日 ead. c ) List 「 2HC ディスケットが 2HC ドライプに入って います」とシステムに知らせます。 BIOS コ ールの仕様は TabIe 3 のとおりて、す。ドライ プ 0 が A ドライプ , 1 が B ドライプに相当しま す。 2 / * F 1 oppy Read Tes t program * / #include く stdio. h> # inc 1 ude く dos. h> #define SSIZ 1024 / * 256 , 512 , or 1024 * / / * 256 : 0 , 512 : 1 , 1024 : 2 * / #define SCNT 2 void main(), dump(); int fddread ( ) : void main() static unsigned char ddptload[ 3 ] [ 11 ] ー { 0xdf, 0X02 , 0X25 , 0X01 , 0x1a, 0x1b, 0xff, 0X36 , 0xf6, 0x0f, 0X08 } , / * 256 * / / * 512 * / 0xdf, 0X02 , 0X25 , 0X02 , 0x0F, 0x2a, Oxff, 0X54 , 0xf6, 0x0f, 0X08 / * 1024 * / 0xdf, 0X02 , 0X25 , 0X03 , 0X08 , 0X35 , 0xff, 0X74 , 0xf6, 0x0f, 0X08 unsigned char buf[ 1024 ] , ddptsav[ 11 ] : unsigned char —far *ddpt; int i ; ddpt = *(unsigned char -far * -far * ) 0X00000078L : for ( i = 0 ; i く 11 ; i + + ) { ddptsavC i ] = ddpt[ i ] ; / * save current FDD param. * / ddpt [ i ] = ddptload[ SCNT ] [ i ] ; / * set FDD param * / セクタのリード・ライト , こて、実際のリード・ライトを行うわけ てすが , これもドライプタイプの設定と同 様に PC/AT マシンの BIOS コール (int 13h ) を使うことがて、きます。 BIOS コールの仕様 は Table 4 , Table 5 のとおりて、す。ドライ プ 0 が A ドライプ , 1 が B ドライプに相当しま ドライプ番号は 0 か す。トラック , ヘッド , ら始まりますが , セクタ番号だけは 1 から始 まりますから注意してください。 BIOS コー ル実行後のステータスパイト ( AH レジスタ ) の意味は , Table 6 のとおりて、す。 DDPT を元に戻す / * read sector * / / * if no error, dump * / if( !fddread( buf ) ) { dump( buf ) ; for( i ニ 0 ; i く 11 : ddpt[ i ] ニ ddptsav[ i ] : / * restore FDD param. * / int fddread( buf ) unsigned char *buf; union REGS rgo, rgi; struct SREGS sr; / * set FDD mode * / rgo. h. ah = 0X17 : / * mode 2HC/2HC * / 「 go. h. al = 0X03 : / * drive no. 1 * / rgo. h. dl = 0X01 ; int86( 0X13 , &rgo, &rgi ) : / * call FDD-BIOS * / if( rgi. h. ah = 6 ) { / * med i a changed ? * / int86( 0X13 , & 「 go , &rgi ) ; / * re-do command * / if( rgi.h. ah ト 0 ) { printf( "Error ! AH = % 02X , AL = % 02X \ n ” , rgi. h. 曲 , rgi. h. al ) ; return( -1 ) ; / * seek and read り rgo. h. ah : 0X02 ; / * read lsct * / rgo. h. al = 0X01 : / * buff. addr * / rgo. x. bx = ( 凹 si ) b 減 , / * track 1 * / rgo. h. ch = 1 ; / * sector 1 * / rgo. h. cl / * head 0 * / rgo. h. 曲 = 0 : / * drive no. 1 * / rgo. h. dl segread( &sr ) : = sr. ds; sr. es int86x( 0X13 , &rgo &rgi, / * call FDD-BIOS り if( rgi. h. ah ト 0 5 { printf ” Error ! AH = % 02X , AL = % 02X \ n ” , rgi. h. 曲 , rgi. h. al ) : return ー 1 ) ; )else{ return( 0 ) : さて , リード・ライトが無事終了したら , DDPT はちゃんと元の値に戻しておきまし よう。戻しておかないと , 今度は米国形式 の FD が読めなくなります。 C によるプログラム例 文章による説明だけを続けても理解しに くいと思いますのて , 具体的なプログラム 例を示します。 List 2 は , ドライプ 1 (B ドラ イプ ) のトラック 1 , ヘッド 0 , セクタ 1 を読 み込んて、表示するプログラムて、す。 ドライバを作るとき調査用に作ったプロ グラムなのて、 , 256 / 512 / 1024 のどれて、も一 応動作するはずて、す。コンパイラには B 。 rl and C 十十 Ver. 3.0 を使用していますが , MS ー C て、も変更なして、通ると思います。 テパドラへの道 今まて、の説明て、 512 バイト以外のフォーマ ットの FD を読み書きすることはそんなに難 しくないということがわかったと思います。 いちばん難しい実際のリード・ライトはす 72 C MAGAZINE 1992 9 void dump( buf ) uns igned char *buf ; lnt 1 , J; 0123456789ABCDEF \ n ” ) ; printf( ' 00 01 02 03 04 05 06 07 08 09 OA OB OC OD OE OF for( i : 0 : i く SSIZ; i + : 16 ) { printf( ” % 04X : for( j = 0 ; j く 16 : j + + ) { buf[ i + j ] ) : printf( ” % 02X ' for( j ニ 0 : j く 16 : j + + ) { d : buf[ i + ) & & ( d く 0x7f ) ) { putchar( d ) ; )else{ putchar( ' putchar( ' #n' ) ;