講 五ロ を -0 List27 の場合には , 外側の while を認識し す。コンパイラ内部には , C プログラム上の て生成したラベル brkl をラベル用のスタッ ラベル ( 名前 ) とオプジェクトコード上のラ クにブッシュします。最初の break 文を認識 ベルとの対応表があります。コンパイラは したときにはスタックの先頭は brkl て、す。 ラベル , goto 文を認識するたびに対応表を 内側の while を認識したときにも新しいラベ 調べます。 goto 文を認識すると , ジャンプ ル brk2 を生成し , スタックにブッシュしま 先のラベルがすて、に対応表に登録されてい す。ふたつ目の break 文を認識したときのス るかどうかを調べます。登録されていると タックの先頭は brk2 て、すから , この break きには , そのラベルへのジャンプ命令を生 文は brk2 へのジャンプ命令にコンパイルさ 成します。登録されていないときには , オ れます。内側の while 文が終了したときには プジェクトコード上のラベルをつくり , ラ ラベル brk2 を生成し , スタックからポップ ベル名といっしょにして対応表に登録しま します。するとスタックの先頭は brkl にな す。そのときに , 「ラベルは定義されている」 りますから , 3 つ目の break 文は brkl へのジ ということを表すフラグをオフにしておき ャンプ命令にコンパイルされるという具合 ます。対応表への登録の後 , そのラベルへ て、す。 のジャンプ命令を生成します。 List 28 は break 文と continue 文をコンパ ラベルを認識したときにも対応表を調へ イルした例て、す。 ます。まだ登録されていないときには , オ プジェクトコード上のラベルをつくり出し , ックにブッシュします。構文の終了を認識 名前といっしょに対応表に登録します。 goto したときには , ラベルを生成したうえて、 , 文のときとは違って , 「定義されている」フラ goto 文はラベルをつけられた文へ制御を そのラベルをスタックからポップして捨て グはオンにします。登録されていたときに 移します。お察しのとおり , goto 文はジャ ます。 break 文や continue 文はスタックの はまずフラグを調べます。フラグがオンに 先頭にあるラベルへのジャンプ命令にコン ンプ命令にコンパイルされます。ラベルは なっているときにはラベルの二重定義の工 goto 文のジャンプ先を示すために使われま パイルします。 ラーて、す。オフになっているときには , 登 録されているオプジェクトコード上のラベ ルを生成し , フラグをオンにします。 関数が終了したとき , ラベルの対応表を 調べ , 「定義されている」フラグがオフにな っているラベルがないかどうかを調べます。 そのようなラベルは「未定義」なまま goto 文 て、使用されたということて、すから , 工ラー を報告します。 中 ・ー・ , ー 1 ・ 0 乙ワっ 0 っ 0 14 1 ・、 1 プ ル 重 多 List 27 break : (b) (a) と等価なプログラム while ( ・・ goto brkl : while ( " goto brk2; brk2 : goto brkl : brkl: goto , ラベル break 文 , continue 文のコンノヾイ丿」 (a) C プログラム i nt i , j, k , List 28 十 く、ー . 0 0 0 E 「 1 ・ おわりに (b) コンパイル結果 WORD PTR _i, 0 SHORT $ F105 Jmp $ FC106 : ax, WORD PTR OV WORD PTR _i CMP Je $ し 20000 ax 靆 00 Jg $ FB107 dec WORD PTR _j $ し 20000 : WORD PTR _i $ F105 : ax, WORD PTR _k 第 0 WORD PTR CMP 引 $ FC106 $ FB107 : ret 制御文のコンパイルとその最適化につい て見てきましたが , いかがだったて、しよう か。コンパイラの性能はもつばら式のコン パイルて、計られていますが , 制御文のコン パイルがダサいと , いくら式のコンパイル て、がんばっていても無意味になってしまい ます。制御文の最適化は単純なだけに , ぜ ひとも今回述べた程度の最適化は実行した いものて、す。 conti nue, break . con い nue 用のラベル , break 用のラベル 128 CMAGAZINE 19 1
fo 「文のコンノヾイ丿」 List 1 4 (a) C プログラム main() for ( i 1 ; i ← (b) コンパイル結果 jle $ FC106 WORD PTR _i ax,WORD PTR WORD PTR _i add WORD PTR _v ax,WORD PTR SHORT $ F105 WORD PTR _i, 1 WORD PTR _v, 0 cmp MOV $ F105 : lnc 田 OV $ FC106 : Jmp 田 OV 凱 OV 2. 文の後に式 3 がある i 十十 ) JÜIP : jt し 2 の 2 点だけて、す。この点に注目して List8 の ようにコンパイルしてみると , List 13 にな ります。ループを継続する条件式は for 文て、 は式 2 て、す。 List 8 とよく見比べてくださ い。違いは上に挙げた 1 と 2 て、すね。 どうて、すか。 List12 よりも List13 のほう が格段に読みやすいて、しよう。さらに , ジ ャンプ命令も少ないうえ , ループの中て、実 行されるのは条件つきジャンプ命令がひと つだけて、すから効率もいいわけて、す。 このように for 文が効率よくコンパイルて、 きるのは , 「 C て、は for 文は while 文の変形に すぎない」というのがその理由て、す。 BASIC や PASCAL のような for 文をコンパイルする 場合 , どのようなコードを生成すればいい のか考えてみませんか ? 勉強になります ループを形成するために使用されます。 do 文 (List15) も while 文や for 文と同じ do て、コンパイルしています。 コードを生成しました。やはり List 13 の形 イルする場合は , List14 のコンパイル例の さて , 実際のコンパイラが for 文をコンパ CMAGAZINE 19 1 しかし , C プログラムの中ては while 文や for 124 文ほど使われてはいないようて、す。 do 文は while 文と似ています。式が真のあ List 1 7 do 文のコンノヾイ丿リ ( MS ー C ) 真のあいだループする 式を評価するコード 文のコード List 1 6 do 文のコード wh ⅱ e ( 式 ) : do List 1 5 do 文 すてに述べたとおり , do 文と while 文との違 てみると , その類似性に気がっくて、しよう。 List 16 と while 文のコード List8 とを比べ 成して終わりて、す。 すから , LI へもどる条件ジャンプ命令を生 す。式の値が真て、あればループは継続しま を生成し , 継続条件式のコードを生成しま にラベル LI を生成します。次に文のコード List 16 を見てください。まずループのため 素直にコンパイルすればよいのて、すから。 d0 文のコンパイルはいたって簡単て、す。 してしまいます。 ば , 文は 1 回も実行されないて、ループを終了 文て、は最初に式を評価した結果が偽てあれ 必ず 1 回は実行されるという点て、す。 while しかし , while 文と異なるのは , 文は最初に いだループを繰り返し , 文が実行されます。 (b) コンパイル結果 } ⅶⅱ e ( i + + ← v) : do { ma i n ( ) (a) C プログラム jle $ D105 cmp ax, WORD PTR WORD PTR - i add WORD PTR v ax, WORD PTR ax, WORD PTR WORD PTR _v. 0 lnc mov $ D105 : mov mov _V いは「まず文が実行されるかどうか」だけて、 すから , do 文て、は List8 の先頭にある無条件 ジャンプ命令が不必要になります。それに ともないラベルも不必要になるという て、す。 List17 ( a ) のプログラムをコンパイル すると List 17 (b) のコードが得られました。 switch たしかに List 16 の形になっています。 ドレスになります。つまり , 式の値がどの ときには , Ln 十 1 は switc れ文の次の文のア との switch 文に default ラベルがなかった ついた文 n 十 1 のアドレスて、す。 成します。ラベル Ln 十 1 は default ラベルが default ラベルへの無条件ジャンプ命令を生 れを値 n まて、繰り返して生成したあと , 文 1 のコードが生成されるアドレスて、す。 きには jt 命令て、 LI へジャンプします。 LI は 生成していきます。値 1 と比較し , 等しいと との比較命令と条件ジャンプ命令を順番に コードを生成します。次に case ラベルの値 のようになります。まず式の値を評価する これを素直にコンパイルすると , List 20 了します。 ときには , 何も実行しないて、 swit 曲文を終 の値がどの case ラベルの値とも一致しない されます。 default ラベルがなく , しかも式 ば , default ラベルのついた文 n 十 1 から実行 す。どの case ラベルの値とも等しくなけれ れ・・・・・・値 n と等しければ文 n から実行されま 実行され , 値 2 と等しければ文 2 から実行さ 較していきます。値 1 と等しければ文 1 から その結果の値と case ラベルの値を順番に比 switch 文は , まず式の値を計算します。 てもひとつだけしか許されません。 ラベルはなくてもよいのて、すが , あるとし case ラベルは同じ値をもてません odefault 数式て、なければなりません。また , 複数の 19 のように使います。 case ラベルの値は定 が , 一般的には文を複文として扱って , List 文は構文的には List18 の形をしています て処理を選択するために使用します。 switch switch 文は , 式を評価し , その値によっ
座 , 五ロ 一 O だし , それを記憶しておきます。そして break 文 , continue 文に出会ったときには , 記憶 しておいた brk cnt ラベルへのジャンプ命令 を生成します。構文の終わりをコンパイラ が認識したとき , brk , cnt ラベルを生成しま す。 ところて、 , 多重ループのなかぞの break 文 は「もっとも内側のループを抜け出す」こと になっています。 List 27 (a) のプログラムは List27(b) のように解釈されます。 List 27 ( a ) のコメントはループのネストを示していま す。 1 の間に break 文が出てくると , そのジ ャンプ先は brkl , 2 の間に出てきた break 文のジャンプ先は brk2 になります。コンパ イラはこれをどのように実現しているのて、 しようか。 外側の w れ ile 文を認識すると , 内部的にラ ベル brkl をつくって記憶します。内側の while 関数からリターンするときには , プロロ 文を認識したときにはラベル brk2 をつくり ーグに対応するエピローグ処理を必ず実行 だすのて、すが , ここて brkl を捨てるわけに しなければなりません。つまり , return 文 はいきません。なぜなら , 内側の while 文が は「エピローグ処理へのジャンプ命令」にコ 終了した後の break 文は brkl へのジャンプ ンパイルされるのてす。 命令になりますから , brkl は覚えておかな さて , List 25 (a) のプログラムをコンパイ ければなりません。内側のループの間だけ , ルすると , List 25 (b) のコードが得られまし break 文のジャンプ先カ市 rkl から brk2 にな た oreturn(i); がエピローグ処理へのジャン るのて、す。 プ命令になっているのがわかります。 このようなデータはスタックを使うとう break,continue まく管理て、きます。ループや switc れの構文 を認識するたびに , 生成したラベルをスタ break 文と continue 文は「行き先が決まっ break,continue のジャンプ先 ている goto 文」て、す。 break 文はループ , も しくは switch 文からの抜け出しのために continue 文はループの再実行のために使わ れます。 これらは goto 文と同じくジャンプ命令に コンパイルされます。 goto 文と異なるの は , ジャンプ先をコンパイラが管理する点 てす。 K & R にも明記されているように ,break 文は List 26 のラベル brk への goto 文と , con tinue 文はラベル cnt への goto 文と同じて す。 コンパイラはループや switch の構文を認 識すると , brk , cnt ラベルを内部的につくり プロローグとエピローグ (a) C プログラム int main(int argc, char *argv[)) / * 関数本体は省略 * / List 24 int i; (b) 生成されたコード PUB い C _maln PROC NEAR _maln push mov sub push push prologue 処理 : パラメータアクセスの準備 , 変数領域の確保 レジスタの保存 , 関数本体 epilogue 処理 レジスタの復帰 , 変数領域の解放 : パラメータアクセスの後始末 0 0 0 0 ENDP _ma ln 3. パラメータアクセスの後始末 といった関数終了のための処理 ( List24 ) が 必要になります。この初期化の処理を pro logue ( プロローグ ) , 終了の処理を epilogue ( 工ヒ。ローグ ) と呼びます。 「 eturn 文のコンノヾイ ) のリ List 25 (a) C プログラム main() int i 十十 ) for ( i = 0 : i く 100 : return (i) : return (-I) : List 26 while ← (b) コンパイル結果 push bp, sp mov sub sp, 2 WORD PTR [bp-2] , 0 mov SHORT $ F104 JÜIP $ FC105 : ー 2 ] WORD PTR [bp $ F104 : WORD PTR [bp-2]. 100 cmp $ FB106 J ge ax, WORD PTR IOV WORD PTR [bp ー 2 ] $ FC105 Jne ax, WORD PTR [bp-2] 田 OV SHORT $ EX102 JÜIP $ FB106 : ax. ー 1 mov $ EX102 : sp, bp mov bp POP ret cnt: brk: do { cnt: } while ( ・ brk: cnt: brk.• プロローグ処理 return switch ( ・・ brk: return エピローグ処理 C 言語雑学講座 127
の処理の開始アドレス ( ラベル ) を入れてお きます。 Fig. 1 の 1 ~ n がそれて , それぞれ LI ~ Ln が入っています。 case ラベルの値て、は ないところには , default ラベルに対応する 処理の開始アドレスを入れておきます。 Fig. 1 ・・には Ln 十 1 が入ってい の 0 , n 十 1 , n 十 2 ・・ ますね。この値とアドレスの対応表を利用 すれば , 計算した式の値をインデックスと して配列参照するだけて、ジャンプ先のアド レスが得られます。 しかし , この方法て、はテープルの大きさ が問題になります。たとえば , すべての値 return に対して表をつくろうとすると int , pointer ともに 16 ビットのコンパイラて、は , アドレ ス 2 バイトが 65536 工ントリ必要になります return 文は関数を終了し , 呼び出したと から , テープル全体の大きさはなんと 128K ころへもどるときに使います。値を返すと バイト / まったく使いものになりません。 きには テープルの大きさだけを見るとオハナシ return 式 ; にならないこの方式て、すが , 場合によって と書きます。値を返さない void の関数から はうまく利用することがて、きます。 case ラ のリターンて、は式は書きません。 ベルの値が密集しているときには , テープ 余談て、すが , return 文の式には文法上は ル参照が効果を発揮します。 カッコをつける必要はありません。 密集とは , たとえば , 1 から 20 まて、の連続 return ( 0 ) ; した値を判定したいときなどて、す。ひとつ と書いても ふたつは抜けている値があってもいいのて、 return 0 ; すが , 1 , 10 , 100 のようにとびとびの値の て、もよいのて、す。 0 は定数式て、すし , 式にカ 場合は密集していないと考えられます。 ープルをつくります。 case ラベルに現れな ッコをつけた ( 0 ) もやはり式て、す。 密集した case ラベルの値のうち , もっと い値に対応するエントリには ,defaultf& 理 直感的には return 文は機械語命令の も小さいものを min, もっとも大きいものを のアドレスを設定します。このときはテー RET にコンパイルされそうて、すが , 実はそ プルのエントリ数は max ー min 十 1 だけて、す max とします。このときに Fig. 2 のようなテ うて、はありません。 return 文はジャンプ命 みます。 case ラベルの数とそれほど違わな 令にコンパイルされます。 switch 文のコード ( その 2 ) いエントリ数て、すから , 「比較してジャンプ 関数のコンパイルて、は , 関数内に記述さ を繰り返す」コードのバイト数よりも小さく れている式や文はもちろんて、すが , そのほ なる可能性は十分あります。 かにも生成しなければならないコードがあ このテープルを参照する switc れ文のコー ります。 ドは List 23 のようになるて、しよう。この方 まず , 関数の最初には 式ては switch 文の実行速度は速くなるう 1. パラメータのアクセスのための準備 え , サイズも小さくなる可能性を秘めてい 2. 変数領域の確保 ます。しかし , 実際にはこのようなコンパ 3. レジスタの保存 イルはあまり実行されていないようて、す。 などの初期化のためのコードを生成しなけ やはり case ラベルの値をいちいちチェック ればなりません。そして関数が終了すると したりするのがたいへんなのて、しよう。 きには , 1. レジスタの復帰 2. 変数領域の解放 switch 文のコンバイ丿リ テープル参照の switch 文のコード 式を評価するコード イ直く m i n ? jt し n + 1 : default イ直〉 max ? jt し n + 1 : default jmp テープル [ 値ー min] 文 1 のコード 文 2 のコド 文 n のコード 文 n + 1 のコード List 23 List 22 (a) C プログラム main() し 2 : break : case ー 1 : case 2 : し n: し n + 1 : break,• default: : defaul t の処理 (b) コンパイル結果 ax, WORD PTR mov ax, ー 1 Je $ SC109 or ax, ax 引 $ SD110 ax, 1 cmp jle $ SC108 ax, 2 Je $ SC109 SHORT $SDI 10 JTIP nop $ SC108 : WORD PTR _j,-l IOV SHORT $ SB105 Jmp $ SC109 : WORD PTR _j, 1 mov SHORT $ SB105 Jmp $ SD110 : WORD PTR _j, 0 mov $ SB105 : ax, WORD PTR _j 田 OV WORD PTR _i , ax 田 OV List 21 式を評価するコード 値 1 と等しいか ? 文 1 のコード jmp し C ー 2 値 2 と等しいか ? 文 2 のコード jmp し C -3 値 n と等しいか ? jf し n + 1 文 n のコード jmp し C - n + 1 文 n + 1 のコード し C -2 : し n : し Cn : し n + 1 : し C - n + 1 : CMAGAZINE 19 1 126
座 五ロ cmp ax , 0 と同じ意味なのて、すが , or 命令のほうが cmp 命令よりもバイト数が少なく , 実行も速い のて、す。式の最適化の一種て、す。 switch 文のコンパイルて、は List 20 の方法 がわかりやすいのて、すが , 難点は比較命令 を繰り返す点て、す。式の値が値 1 のときには 最初に判定されますから実行時間はもっと も短いのて、すが , 値 n のときには最後に判定 されますから , n 回の比較命令と条件ジャン プ命令を実行しなければならず , 実行時間 がもっとも長くなります。 switch 文の効率を上げるインプリメント のコードを生成していました。 List 21 て、は のひとつの方法は , テープル参照方式にコ 比較命令と文のコードを一組にして生成し ンパイルすることて、す。 case ラベルの値が ています。 1 ~ n て、あるとします。このとき , Fig. 1 のよ List 21 のプログラムは次のように実行さ うなジャンプ先アドレスの配列をつくりま れます。まず式を評価して , 値 1 と比較しま す。値をインデックスとする要素には , そ す。一致しなければ次の比較へとジャンプ します。一致しているときには文 1 のコード switch 文 case ラベルとも一致しないときには , switch こて、 List20 とは異な が実行されます。 文の本体は実行されずに終了します。 り , そのまま次の文 ( 文 2 ) を実行するために ところて、 , List 20 には文のコードの後ろ ジャンプ命令を生成しなければなりません。 に swit 曲文から抜け出すためのジャンプ命 このジャンプ命令が j Ⅲ P LC 2 て、す。ラベル 令がないことに注意してください。つまり , LC 2 は文 2 のコードの先頭アドレスを表し Lnext を switch 文の次の文のアドレスを示 ています。このジャンプ命令がないと , そ すラベルとすると , のまま値 2 との比較命令が実行されてしまい ますね。これを一組として , 「比較と実行」 LI: 文 1 のコード を繰り返しながら最後まて、いけば , switch 文の処理が実行て、きます。 jmp Lnext ただし , List21 のプログラムて、はジャン プ命令の数が List20 よりも多いため ( ほば 2 文 2 のコード 倍になっています ) , 実行時間もサイズも効 jmp Lnext 率は悪くなっています。 のようになっていない点て、す。 実際にコンパイルした結果が List 22 て、 このようなジャンプ命令は break 文を使用 す。 List 20 の形にコンパイルしています。 したときにはじめて生成されます。逆にい しかしよく見ると , 比較命令のところが えば , プログラム中てはっきりと break 文を 使わないかぎり , 文 1 を実行した後はそのま List20 とは徴妙に違っていますね。 List20 て、は「一致したかどうか」を判定しています ま文 2 が実行され , さらに文 n 十 1 まて、順番に 実行されてしまうのてす ocase や default は が , List22 て、は多少複雑な判定をしている ラベル , つまり名札てしかなく , 制御文て ようてす。 はありませんから。 ところて、 , このリストの中に現れている switch 文のコードのもうひとつの例とし or ax, ax て List21 を考えてみましよう。 List20< は というのは ax レジスタが 0 かどうかを調べて 先に比較命令を生成し , 最後にまとめて文 います。 Fig. 1 アドレスのテープル Ln 十 1 LI Fig. 2 密集したラしのテープル mln 0 1 2 max n 十 1 Ln 十 1 n 十 2 Ln 十 1 List 1 8 switch ( 式 ) 文 実際の switch 文 List 1 9 switch ( 式 ) { case 値 1 : 文 1 case 値 2 . 文 2 case イ直 n : 文 n default : 文 n + 1 ・ switch 文のコード ( その 1 ) 式を評価するコード 値 1 と等しいか ? 値 2 と等しいか ? jt し 2 値 n と等しいか ? jt し n jmp し n 十 1 文 1 のコード 文 2 のコード 文 n のコード 文 n + 1 のコード し n: し n + 1 : C 言語雑学講座 125
Obieot 0 0 List 1 9 1 : 10 : } List 20 LiSt 21 selector: :seIector(scrPos& po, char* ttl, char** la) (po, I abel W i d ( I a) , IabeILen (la) , ttl) 3 : 4 : 5 : 7 : 8 : 9 : labels / / ウインドウ内へのラベル表示 w h i I e ( 利 a) *th i s くく * 1 a + + くく 〃現在位置のリセット curPos POS ; "Yn" B>Wi n こて、 , IabelWid と IabelLen selector て、もすべて公開という意味て、あ る。この public を指定しないと非公開導出 たとえ公開メンバて、あって クラスとなり , も基底クラス (base class, 導出クラスの元 になっているクラスをこう呼ぶ ) のメンノヾは 参照て、きなくなる。ただし基底クラスの非 公開メンバは , たとえ公開導出クラスて、あ っても参照て、きない selector のコンストラクタは , 表示位置 とタイトル , それに表示ラベルを格納した NULL て、終わった文字列の配列を引数にも つ List19 のようなものて、ある。 実行する前に , コンストラクタ すなわちコンストラクタ selector は本体を いるのが 1 行目の : 以下の部分て、ある。 引数を明示する必要がある。これを行って て、は , 基底クラスのコンストラクタへ渡す クラスのコンストラクタのインプリメント を読んて、から実行される。このため , 導出 ( もしあれば ) 基底クラスのコンストラクタ 一般に導出クラスのコンストラクタは , を実行する。 ttl) window(po, IabeIWid(Ia), labelLen(Ia), 1 : 3 : 4 : 7 : 8 : 10 : 2 : 3 : 4 : 5 : 6 : static int IabeIWid(char** 語 ) i nt w i d i nt W : char* s; w h Ⅱ e (s ニ * 1 a + + ) / / 幅の計算 w i d static int IabeILen(char** ) / / 長さの計算 int len while (*la + + ) len + + : return len; return wid; = st ⅵ en (s ) ) 〉 w i d) 1 : c lass w i ndow { / / ウインドウ protected: scrPos pos; scrPos curPos; private: i nt co I W i d : nn : p ub 1 i c : / / 半公開メンバ / / 左上の位置 / / 現在の位置 / / 桁幅 int = defWid, window(scrPos&, window() : int ニ defLen, char* はラベル配列引数 la からウインドウ幅と長さ を計算する関数て、ある (List20)0 なお , コンストラクタ selector のなかて、 window の非公開メンノ *curPos と pos を参 照しているが , これは前述したとおり , この ままて、はコンパイルエラーになる。そこて、 , 基底クラス window の宣言のなかて、 pos と curPos を次のように半公開 (protected) メ ンバとして宣言する必要がある (List21)0 て , この配列の大きさが変わるところが問 ある。とくにコマンド行の引数の数によっ コンストラクタに渡すラベル配列の作製て、 ここて、少々めんどうなのは , selector の A 〉記ー This is an object oriented environment 起動方法は以下のとおりて、ある (Fig. 2) : 停止方法は , 右クリックて、ある。 ルを画面の左隅に表示する。プログラムの たときにマウスカーソルが指していたラベ すまて、の間しか存在しない。ボタンを離し ンドウはマウスの左ボ、タンを押してから離 例を示す。今度のプログラム sel て、は , ウィ こうしてつくったクラス selector の使用 プロクラム s のほかの部分て、は非公開として扱われる。 公開メンバとして振る舞うが , プログラム 半公開メンバは , 導出クラスに対しては 第一特集オプジェクト指向プログラミング 55 にメモリ管理のための new と delete という 一般的て、あるが , C 十十て、は言語仕様のなか と free というライプラリ関数を用いるのが 題て、ある。 C の場合 , こうした間題は calloc
List 22 Th i s B > 5 引 This is an object T+E,t Th i s an object 0 「 iented env i main(int argc, char* argv[]) 0 「 i ented e 「Ⅳⅱ、 onrnent 1 : 3 : 4 : 5 : 7 : 9 : 20 : 22 : 23 : 24 : 25 : 26 : 29 : 30 : 32 : 33 : 34 : } 1 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : シェク 〃マウスの状態検査 ニ mouseBad) { if (theMouse. state() theScreen くく " マウス・ドライバが組み込まれていません Yn " ふたつの演算子があるのて、 , 今回はこれを使 うことにする。今ラベル配列の大きさは最 後の NULL を含めると argc 個て、あるから , このために用意する領域 sO は次のようにし て確保する : char * * SO = new char * cargc] また , この領域を解放するには以下のよ うにする : delete sO; このように , calloc や free と違ってキャ ストしたりする必要がない。そのほかにも コンストラクタっき ( デストラクタっき ) の 型のオプジェクトを new(delete) を使って生 成 ( 破壊 ) する場合 , 暗黙のうちに必要なコ ンストラクタ ( デストラクタ ) が実行される という機能もある。 プログラム sel の主プログラムを List22 に 示す。左ボ、タンが離されるまて、待っ関数 waitReIease は , 則述の waitPress を少し 変形したようなものて、ある (List23)0 まめ オプジェクトを中心に据えたこうしたプ ログラミング手法は , まだ一般の C ューザに はなじみが薄いかもしれない しかしなが ら , ちょうど 70 年代初頭から始まった構造 化プログラミングと同様に , 90 年代て、は確 実に主流となるプログラミングスタイルて、 ある。そうしたなかて、 , C 十十が主要な役割 を果たすて、あろうこともまた確実て、ある。 なお , 本稿て、示した例は , 使用法によっ ては妙な動きをしたりして , まだ実用的と はいい難い部分がある。しかし , C 十十のよ うなオプジェクト指向プログラミング言語 を使ってオプジェクトを作製したり , オプ 56 CMAGAZINE 19 1 ex i t ( I) : / / セレクタ用ラベルの取得 if (argc く 2 ) exit(l); new char* [argc] : char** SO char** S i く a rgc : i 十十 ) { for ( i nt i = argv[i]; * S 十十 / / 主ループ scrPos pos; for (; ; / / ラベル格納領域の確保 / / arg[0] は除く if (waitPress(pos) selector aSelector(pos, if (waitRelease(pos) mouseRight) break; = aSe lector (pos) . 1 ( ) : / / 相対行番号の算出 i nt n i f (n く 0 Ⅱ n > ニ a rgc- 1 ) theScreen くく BUZAR; e I se theScreen くく POS. set(0,0) くく s0Cn] : } / / aSe 1 ector のスコープ終わり delete so; / / ラベル格納領域の解放 mouseRight) break; ” Test" List 23 mouseButton waitReIease (scrPos& POS) mouseButton button; theMouse. cursor (ON) : for ( : : ) { / / 左ボタンが離されるか右ボタンが押されるまで theMouse. curPos ( ) : POS if (theMouse. right() ニ (N) { button mouseRight; break; } if (theMouse. left() ニ OFF) { button mouse し eft; break; } theMouse. cursor (OFF) : return button; ト指向な環境を構築したりする手法 のアウトラインを理解するにはこれて、十分 て、あると思う。た十十の入門という趣旨 からいえば , 文法にそった網羅的な解説を しなかったために , 仮想関数 (virtualfunc を参照されたい 残念て、ある。これらについては以下の文献 ような重要な概念を紹介て、きなかったのが tion) や多重導出 (multiple inheritance) の 参考文献 [ 1 ] 足立高徳 ! ℃ + + 入門」 CQ 出版社 , 1988 [ 2 ] 「 MIWA C 十十ューザーズ・マニュアル」ミワシ ステムズコンサルティング , 1988 [ 3 ] Stroustrup , B. , 、、 The C 十十 Programming Language" , Addison-Wesley , 1986 ( 斎藤信男訳 , ープログラミング言語 C 十十』トッパン , 1988 ) 、℃十十 Primer" , Addis [ 4 ] Lippman, S. B. on-WesIey , 1989 [ 5 ] 門内淳 / 赤堀一郎「 C 十十プログラミング』日本 ソフトバンク , 1989
→座 講 を終了するのて、 , L4 へとジャンプします。 続いて式 3 のコードを生成しますが , 式 3 は 文が実行された後に制御が移ってくるとこ ろて、すから , ラベル L2 をつけておかなけれ ばなりません。式 3 によって再初期化を実行 した後は , 再びループを開始するか終了す るかをチェックするために , 式 2 を実行しな ければなりません。そのため , 無条件ジャ ンプ命令 jmp LI が必要て、す。最後に文のコ ードを生成します。文のコードの先頭には , 式 2 の後の条件ジャンプ命令の飛び先ラベル L 3 をつけます。さらに文のコードの直後に は , 再初期化部分て、ある式 3 へのジャンプ命 令 jmp L2 を生成します。最後にループの終 わりを示すラベル L4 を生成して for 文のコ このように , List8 のコードて、はループ 1 ンパイルがてきました。 回に対して jmp 命令 1 個分の時間がセープて、 という使い方をしますね。 これて、ひととおり for 文のコンパイルはて、 きます。「なんだ , たいしたことないじゃな さて , for 文のコンノヾイルについて考えて きたのて、すが , どうもジャンプ命令だらけ いか」と思うかもしれませんが , これが何千 みましよう。最初は単純なコンパイル例て、 て、制御があっちこっち飛び回って見苦しい 回もまわるループだったらどうて、しよう。 て、すね。もっとすっきりしたコンパイルは す。 List 12 を見てください。式 1 は , ループ ほとんどのプログラムは実行時間の 90 % を に入る前に 1 回だけ評価すればいいのて , ま て、きないものて、しようか。 コードの 10 % のループて、費やすといわれて ず式 1 のコードを生成します。次は式 2 のコ List 11 の for 文と等価な while 文を思いだ います。プログラムの実行時間を短くする こからがループの本体にな ードて、すが , してください。これをもとにして , for 文を ためには , ループを最適化するのが非常に りますから , ラベル LI が必要て、す。 LI には 「 while 文として」コンパイルしてみるとどう 有効て、す。さて , List9 は while 文をコンパ なるて、しようか。 while 文そのもの (List 5 ) 式 3 を評価した後に制御が渡ってきます。式 イルした例て、す。 List8 の形式にコンパイル と for 文と等価な while 文 (List (1) との違い 2 を評価した結果が真て、あれば , 文を実行し されているのがわかります。 ますから , L3 へとジャンプする条件ジャン は , 1. while の前に式 1 がある プ命令を生成します。偽のときにはループ for 文のコード ( その 1 ) 式 1 を評価するコード 式 2 を評価するコ jt L3 jmp し 4 式 3 を評価するコード jmp し 1 文のコード jmp し 2 wh ⅱ e 文のコンノヾイ ) リ ( MS ー C ) (a) C プログラム fo 「文 List 1 0 List 9 for ( 式 1 : 式 2 : 式 3 ) 文 main() while (i く j) fo 「文と等価な wh ⅱ e 文 List 1 1 式 1 : wh ⅱ e ( 式 2 ) (b) コンパイル結果 WORD PTR _v, 0 田 OV SHORT $ し 20 圓 1 JÜIP $ FC106 : ax, WORD PTR 田 OV WORD PTR _i add WORD PTR _v $ し 20001 : ax, WORD PTR mov WORD PTR _i $ FC106 文 式 3 : JIP あるいは while : jt し 2 fo 「 List 1 2 for 文は List10 の形をしています。意味的 には List 11 の while 文と同じて、す。 for 文て、は , まず初期化のために式 1 が評 価されます。続いて式 2 が評価され , 真の間 だけ文が実行されます。文が実行された後 て、再初期化のために式 3 が評価され , それか ら再び式 2 が評価されます。 ただ , for 文と w ⅲ le 文が異なるのは , 式 を省略て、きる点て、す。 while 文て、は式は省略 て、きません。 for 文て、式 2 を省略したときに は「真」と評価されます。無限ループをつく るときに , よく 真のときはし 3 へ : それ以外はループを終了 : ループ継続の判定へ . 再初期化へ し 4 : fo 「文のコード ( その 2 ) 式 1 を評価するコード jmp し 2 文のコード 式 3 を評価するコード 式 2 を評価するコ jt い List 1 3 : ループへ飛び込む 真のあいだループする C 言語雑学講座 123
制御文のコンバイル 本ロ 0 五ロ 乗松保智 今回は , 制御文かどのようなアセンプリ言語のコードに変換される のを見なから , その働き・機能を解説したいと思います。ループ制 御がどのようなコードに展開されるのか , はなはだ興味深いところ でしよう。もちろん , C の文法仕様とは , 直接の関係はありません が , コンバイラがどのような最適化をしているのか ( あるいはして いないのか ) かだんだんとわかるようになります。 内部の動作なども考えながら , どういうア はじめに センプリ言語にコンパイルされていくのか を解説してみようと思います。 コンパイラの性能を評価するときに , 「コ ンパイル結果のアセンプリ言語をながめて みる」ということをよく行います。多くの場 if 文 ( List1 ) て、は式の値が真のとき ( 0 て、な 合この評価て、注目されるのは , 式がどのよ いとき ) には文 1 が実行され , 偽のとき ( 0 の うなアセンプリ言語にコンパイルされてい とき ) には文 2 が実行されます。 else と文 2 は という点てす。レジスタはうまく利 るか , 省略することがて、きます。 用されているか , また , 適正な命令が使用 それては , List1 の if 文がどのようにコン されているかを評価します。このように パイルされるかを調べてみます。 if 文を素直 式についてはいろいろと評価記事などもあ にコンパイルしてみると , List2 のようにな りますから , 式がどのようにコンパイルさ るてしよう。ここて jt は条件が真だったとき れるかをご存じの方も多いて、しよう。 にジャンプし ( jump if true), jf は条件が しかし , 制御文 (if, while など ) がどのよ 偽だったときにジャンプする (jump if うにコンパイルされるかをご存じない方は false) 条件っきジャンプ命令 , jmp は無条件 意外と多いのて、はないて、しようか。制御文 ジャンプ命令とします。 のコンパイルは , 式のようにレジスタを利 List2 はまず式を評価します。その結果が 用するわけて、もなく , 比較演算と条件っき 真て、あれば jt 命令によってラベル LI にジャ ジャンプ命令とを組み合わせることによっ て、きていないということになりますから注 ンプし , そうてなければ偽なのて、ラベル L2 て , わりと単純に高級言語からアセンプリ にジャンプします。 LI て、は文 1 に対応する機 意してください 言語にコン六イルてきます。しかし , 制御 式が偽の場合には制御は L2 に渡ってきま 械語を実行し , この if 文から抜け出すために 文は ~ ープをを成しますから , ちょと頭 す。ここには文 2 に対応するコードが配置さ L3 へジャンプします。 L3 は if 文の終わりを を使ってコートを生成しないと , 非常に効 れていて , else 節のプログラムが実行され 表しています。もしもこの jmp L3 がない 率が悪くなってしまいます。 ることになります。 if 文を抜けるための jmp と , 文 1 を実行した後 , 続いて文 2 まて実行 そこて、 , 今回はプログラムの実行の流れ L3 は不必要てす。なぜなら , ラベル L3 はこ してしまうことになり , 正しくコンパイル を制御する「制御文」について , コンパイラ C 言語雑学講座 121 if 文 List 1 if ( 式 ) 文 1 else 文 2 / * else 節はなくてもよい * / if 文のコード ( その 1 ) List 2 式を評価するコード 真のときはいへ . それ以外はし 2 へ : i f 文を終了する jmp し 2 文 1 のコード jnp し 3 文 2 のコード し 3 : if 文のコード ( その 2 ) 式を評価するコード 文 1 のコード jmp し 3 文 2 のコード List 3 偽のときはし 2 へ : i f 文を終了する し 2 :
必要がないときは何もしない クラス mouse には , マウスボ、タンの状態 を示すふたつのメンノヾ IeftButton と right Button があるが , これらは以下の公開メン バ関数 curPos を呼び出すたびに改訂され る。またこの関数は , 戻り値としてそのと きのマウスカーソル位置を返す ( List16 ) 。 プ等グラム win 前節まて、につくったクラス window や mouse の使用法を見てみる。 こて、つくる プログラム win は , マウスを左クリックする とマウスカーソルの位置にウインドウを開 き , もう一度左クリックするとそのウイン ドウを閉じる。プログラムを停止するには , 右クリックする。 主プログラムは List17 のとおりてある。 こて、関数 waitPress は , マウスボ、タン が押されるまて、侍ち , そのときのマウスカ ーソルの位置と押されたボ、タン ( 左か右 ) の 種別を返す List18 のようなものて、ある。 主プログラムの主ループのなかて、 1 回目 の左クリックの後に宣言されたウインドウ aWindow は , 宣言と同時にコンストラクタ が実行されて画面にウインドウ枠が表示さ れる。そして , 2 回目のクリックの後て、 , aWindow のスコープを飛び出すと , デスト ラクタが実行され枠が消去される (Fig. 1) 。 セクタ クラス window は , たんにウインドウの枠 を設定してそこに文字列を書き込む手段の みを提供していない。そこて、この節て、は , この window に機能を追加してポップアップ メニューによるメニュー選択に利用て、きる 新しい型 selector を作製する。この型は window にメニュー用のラベルを表示する機 能を付け加えただけのものて、ある。このよ うに既存の型に一部だけ機能を追加してよ り特殊な型をつくるには , オプジェクト指 向プログラミングの「導出 (derivation) 」と呼 ばれるテクニックが利用て、きる。 C 十十には 54 CMAGAZINE 1990 1 LiSt 3 : 4 : 5 : 6 : 7 : 8 : 9 : LiSt 1 : 3 : 4 : 6 : 7 : 8 : 9 : List 1 : 2 : 4 : 5 : 6 : 7 : 1 6 1 : s crPos & mou se : : c u r P os ( ) / / カーソル位置の取得とボタン状態の改訂 if (state ( ) ! = mouseBad) { reg. X. ax int86 (mouseBios, ®, ®) : (reg. x. (x) ? ON : OFF; leftButton (reg. x. (x) ? ON : OFF,• rightButton pos. set((reg. x. (x)/16, (reg. x. (x)/8) : return pos; 1 7 main(int argc, char* argv[]) / / マウスの状態検査 if (theMouse. state() mouseBad) { theScreen くく " マウス・ドライバが組み込まれていません Yn " ex i t ( 1 ) : / / 主ループ scrPos pos; for ( : ・ if (waitPress(pos) mouseRight) break; window aWindow(pos)i// ウインドウ aWindow の宣言 if (waitPress(pos) } / / aW i ndow のスコープ終わり ー mouseRight) break; 1 8 mouseLeft; break.; } mouseRight; break,• } ニ (N) { button ニ (N) { button ー if (theMouse. left() if (theMouse. right() POS theMouse. curPos() : for ( : : ) { 〃左ボタンか右ボタンが押されるまで theMouse. cursor (ON) : mouseButton button; mouseButton waitPress(scrPos& pos) enum mouseButton { mouse し eft, mouseRight } : return button; theMouse. cursor (OFF) : このテクニック実現のために , 導出クラス (derived class) という手法が用意されてい る。この手法を使ったクラス selector の定 public: char * * labels; private: class selector : public window 〃セレクタ 義は以下のとおりて、ある : selector(scrPos&, char * , char * * ) ; クラス selector は , window にデータ labels と新しいコンストラクタを付加して 宣言されている。 1 行目の , class selector : public window て、 , selector が window の公開導出クラスて、 あることを示している。 こて、 public が示 す「公開」とは , window の公開メンバは