buf - みる会図書館


検索対象: 月刊 C MAGAZINE 1991年11月号
14件見つかりました。

1. 月刊 C MAGAZINE 1991年11月号

最新ロロロレポート Turbo C を使う場合 , 統合環境上て、ライ 線・円を描いたもの , Fig. 4 がその実行結果 知識は必要だ。 てある。 プラリ CLIPS. LIB を使ってコンパイルする ユーティリティとして , 「エミュレーショ とメモリ不足となるため , 必ずコマンドラ べクタモードて、のスケーラブルフォント ン / LIPS 切り替え」を実行するプログラム L IPS.COM が付属している。筆者は「一太郎 V インから tcc コマンドて、 , オンメモリコンパ 印字は LIPS コマンドを使う場合 , 手順が複 イルしなければならない。また , 付属の M 雑だが , 拡張関数て、比較的簡単に印字て、き er. 3 」用のバッチファイルに組み込んて使っ AKEFILE. TC て、ライプラリ化するときは T る。ただし , この場合て、も LIPS コマンドの ているが , とても重宝している。 urbo C て、はなく MS-DOS の MAKE コマンド 直線・折れ線・円を描くサンプルプログラム を使うことが必要だ。 ほかにキヤノンが販売している , 『クック ブック』の内容て、あるレッスンプログラム のソースプログラムが一部添付されている のて、 , 初心者には参考になるだろう。 対象ユーザは , 「 C 言語に熟知していて , LIPS をある程度理解している人」て , なおか つ「レーザショットが手元にある人」となり , ューザはかなり限られる。一般ユーザより も , レーザショット対応ソフトの開発を行 う会社などの法人ユーザがおもな対象とな るだろう。なお , A ー 404 モデルの登場て , レ ーザショットがパーソナルュースにも使わ れるようになっているため , LIPS を直接制 御したいと考えている熱心なユーザには「 C LIPS 」は福音となるだろう。 LIPS を熟知したユーザがどれくらいの数 いるかは知らないが , このライプラリによ り LIPS やページ記述言語を理解する上て役 に立つだろう。 TCLIPS 』 対応機種 MS-DOS マシン , UNIX マシン く価格 > MS-DOS 用個人購入 32 , 800 円 法人購入 58 , 000 円 UNIX 用個人購入 98 , 400 円 法人購入 174 , 000 円 く問い合わせ先 > 株フレイア 〒 274 千葉県船橋市三山 5 ー 3 ー 5 TEL 0474 ー 70 ー 1064 LiSt 1 : / * C L I P S ライブラリ・テストプログラム事事 / 2 : #include く stdio. h> 3 : #include く ctype. h> 4 : #include ” clips. h ” 5 : 6 : void nain() 8 : coordinates buf[ 1 の : 9 : / 事 LIPS Ⅲモードで印刷事事神・事事神事神韓事 / 10 : 1 0 n ( ” T t ” , ” 988 「 t ” . Lips3) : 11 : IpsVector(); 12 : lpsLineWidth(30L) ; 13 : 1 Line ( 2000L , 2000 し 2000 し 8000L ) : 14 : lpsLine ( 2000 し 5000L , 8000 し 5000L ) ; 15 : IpsLineWidth(50L); 16 : lpsCircle(6000L, 4000L , 2000D ; lpsLineWidth(100L); 17 : 18 : PiIIHorizontalLine,Nornal) : 19 : buf[ 0 ]. x = 20001 ;bufC 0 ]. y = 50001 ; 20 : buf[ 1 ]. x = 30001 ; buf [ 1 ]. y = 25001 : 21 : buf[ 2 ]. x = 40001 : b [ 2 ]. y ニ 22001 : 22 : buf[ 3 ]. x = 50001 ; buf [ 3 ]. y = 26001 ; 23 : bufC 4 ]. x = 60001 : b 減 [ 4 ]. y = 31001 ; 24 : buf[ 5 ]. x = 70001 : b Ⅱ f [ 5 ]. y = 50001 ; buf[ 6 ]. x = 80001 : buf [ 6 ]. y = 80001 ; 25 : IpsPoIyLine(), buf); 26 : / 事印刷処理終了 * 事本神神神”本事事事・ / 28 : IpsText() ; 29 : IpsClose() ; 30 : ) Fig. 4 直線・折れ線・円の印字例 最新開発環境レポート 151

2. 月刊 C MAGAZINE 1991年11月号

List 3 クソートによる事前ソートを行なうことて、 す。 主記憶の残り領域を目一杯使って , それ だけの長さの整列をまず作ってしまいます。 こて、 , 関数 buf_init ( ) と generate run ( ) が活躍します。また , 最初の整列の後て、 , ファイルに書き込まれた最後の項目を記録 しておくことが必要て、す。あとて、それを , 次の整列をファイルに書き込むとき , その 最初の項目と比較します。次に生成される 整列が , 前の整列の続きて、ある , という場 合もありえます ( 整列済みファイルなどの場 合 ) 。この場合 while ループを使って , 整列 が前の整列の続きて、あるかぎり連続的に書 き込んて、いきます。これが終わると , 整列 をもう一度書き込んて、 , それを単一の整列 として数えます。データ源のファイルを全 部読んて、しまったら , ダミー整列の数をイ ンクリメントします。 ノヾッファ l/ 〇 性能向上のためのもうーっの方法として , 外部記憶装置 ( ディスク装置 ) とオペレーテ イングシステムの実行性能に着目します。 オペレーティングシステムのディスク I / O コ ールを最大限効果的に行なうためには , 幾 つかの大きなバッフアを用意して , マージ 処理て、使う各ファイルにバッフアを一つす つ与えます。バッフアのサイズは , オペレ ーティングシステムにとってのプロックサ イズの倍数とします ( 今回の 64K バイトは , 512 , 1024 , 2048 などの倍数て、す ) 。すべて のディスク I / O が , これらのローカルバッフ アを使う , 大ブロックの read と write リクエ ストて、行なわれるようにします。 こういうバッフアの実装は , 比較的簡単 て、す。やや変わっているのは , read バッフ アに事前にデータを入れておいて , ファイ ルの次の項目にアクセスするとき比較がて、 きるようにしている点て、す。 この他 , 各バッフアの先頭を示す固定的 なポインタを維持します。 read バッフアの sposCn] = rpos[n] = topCn] ; fclose(tmp_files[n]) ; / * ファイルを切り詰める * / tmp—fi les Cn] = fopen(fnamesCn], ” wb + ” ) : if (tmp files[n] ー = NULL) serFor ( OPEN_ERR) ; buf_cnt Cn] 404 : 405 : 406 : 407 : 408 : 409 : 410 : 411 : 412 : 413 : void bflush(n) 414 : int n; 415 : 416 : S 1 ze t W : 417 : mermove(bs, buf, rsize) ; / * フラッシュするのでセーブ ! * / 418 : 419 : buf ニ bs ; w = fwrite(rposCn], rsize, buf_cnt[n], tmp_filesCn]) ; 420 : if (w ! = buf-cntCn)) 421 : serror (WRI TE ERR) ; 422 : fflush()m fiIesCn]) ; 423 : buf cnt 424 : sposCn] ニ rpos Cn] ニ t 叩 (n) ; 425 : 426 : 427 : 428 : void bmove(), m) 429 : int n; 430 : int m; 431 : if (end dataCmJ) 432 : 433 : return; memove(spos[n), next_itemcm), rsize) ; 434 : ニ sposCn] ; 435 : buf (char * ) sposCn] + = rsize; 436 : buf_cnt Cn] + + ; 437 : if (buf_cntCn] = max_items) 438 : bflush(n) ; 439 : (char * ) rposCm] + = rsize; 440 : buf—cntCm) + + ; 441 : if (rposCm) 〉ニ sposCm]) 442 : bfill(m); 443 : 444 : else next_i tem CIII] 445 : 446 : 447 : 448 : void serror(e) 449 : int e; 450 : fcloseall(); 451 : buf_free() ; 452 : printf("error: %d*n ” , e) ; 453 : exit(e) ; 454 : 455 : 456 : 457 : void buf_free() 458 : 459 : register int i ; 0 : free ()s ) ; 461 : for (i i く TMP FILES; 462 : free(last—itemCiJ) ; 463 : for (i i く last_file; 464 : free(topCi)); 465 : free (buf—ptrs ) ; 466 : 467 : 468 : 469 : void quick_sort(), 1 , r, comp) 470 : void *a[]; / * ソートするポインタの配列 471 : int 1 , r; / * 左右のインデクス x 472 : int (*comp) ( ) ; / * 比較関数 473 : 474 : register int i; 475 : register int j; 476 : VOid *fflid; 477 : VOid *tmp ; 478 : s truct s tack 479 : 480 : int 1 ; 481 : int r; 482 : struct stack sChAX STK] ; 483 : 484 : struct stack *SP,• / * バッフアをフラッシュする * / / * バッフアが空 * / / * 目的パッフアのインデクス * / / * 元のバッフアのインデクス * / ニ rpos Cm] ; / * 最小限のエラーハンドラ * / i 十十 ) i 十十 ) / * ローカルスタック * / ソーティングの秘密主義を終わらせよう 37

3. 月刊 C MAGAZINE 1991年11月号

Li st 3 ー山のカードから出発した , と想定して ください。マージ操作をするためには , カ ードを複数の山に分けなければなりません。 最初 , 各山の整列の数が Fig. 4 の上の列のよ うになるように , カードを分けたと想定し てください 毎回のマージて、作られる一つの整列が , 目的位置の一つの整列へとつながれていき ます。最初のマージの後て、は , 整列の数が 最も少なかった山は空になります。前に空 だった山には , 最も整列が少なかった山と 同じ数の整列があり , そして , 他のそれぞ れの山からは , 同じ数の整列がなくなって います。すなわち , 最初は二番目に整列数 が少なかった山が , 今度は最少整列数の山 になります。この処理を Fig. 4 のように続け ると , 最後に 1 枚だけ残り , 他の山はすべて 空になります。これて、 , カードはソートさ れました。 最初からうまくいくように仕組んだのて、 はないか , と疑った読者は正しいのて、す。 トリックは , 最初の各山の整列数の分布に あります。 整列を五つの入力ファイル ( カードの山 ) に配付する場合 , 各ファイルの整列数がオ ーダ 4 のフィポナッチ数の和になるように配 ります ( 一般的に , n 個の入力ファイルに対 してオーダ n ー 1 とします ) 。その意味を , F ig. 5 を使って以下に簡単に説明します。 どのレベルて、も , ファイル 5 ( n ) の整列の 数は , 前のレベルのファイル 1 の整列の数と 同じて、す。さらに , 他のファイル ( 1 , 2 , , ( n ー 1) ) 上の整列の数は , 前のレベル のファイル 1 とファイル (n 十 1 ) の整列数の和 と同じて、す。たとえば , 4 レベル目のファイ ル 4 には , 6 整列があります。これは , 3 レベ ル目て、のファイル 1 と 5 の整列数の和と同じ て、す。最初の第 1 レベルの整列の数はつね ( 1 , 1 , 1 , ・・・ ) て、す。これらの値は , ソートルーチンが実行時に作りだすことが て、きます。 もちろん , これも少々作為的てす。各レ ベルて、完全な分配がて、きるためには , 最初 242 : 243 : 244 : 245 : 246 : 247 : 248 : 249 : 250 : 251 : 252 : 253 : 254 : 255 : 256 : void buf init(fp, co 叩 ) 257 : FILE * 朝 ; / * generate ー run ( ) のためにメモリを取得してハ・ツフアを初期化する * / 258 : int 259 : / * アロケートし , セーフ・したら開放する * / 260 : VOid *t 叩 ; 261 : int 262 : tmp = malloc(HEAP_SAVE) ; 263 : bs = malloc(rsize); 264 : i く TMP_FILES; i + + ) for ( i 265 : 266 : last itemCi] ニ malloc(rsize); 267 : if (TIast—itemCi]) 268 : serror (ALLOC_ERR) ; 269 : 270 : max_items = MAX BUF / rsize; 271 : num—ptrs = MAX ÉUF / sizeof(void * ) ; 272 : buf—ptrs = (void * * ) 1 loc(MAX BUF) ; 273 : if ( ! (buf-ptrs & & bs)) 274 : serror (ALLOC-ERR) ; 275 : for ( i i く TMP_FILES ー 1 ; 276 : 277 : = mal loc(MAX_BUF) ; topCi) 278 : if (topCi] = NULL) 279 : 280 : break ; 281 : topCi] = (void * ) buf-ptrs; / * バッフアの拡大を realloc ( ) を使わずにやる * / 282 : last minus one 283 : 284 : / * マーシ・には少なくとも 3 つのファイルが必要 * / if (i く 3 ) 285 : serror(ALLOC_ERR) ; 286 : / * オペレーティングシステムに返す * / free(tmp) ; 287 : 288 : last file = i; fi 1 IZbuffer(fp) ; 289 : 290 : ー rd count ; switch (rd_count) 291 : 292 : case ー 1 : 293 : end of data ニ 1 ; 294 : 295 : return ; case 0 : 296 : break ; 297 : default: 298 : quick_sort(buf_ptrs, 0 , rd count' CO 叩 ) ; 299 : break ; 300 : 301 : next = buf—ptrsC0] ; 302 : end of data ニ 0 ; 303 : 304 : 305 : 306 : void generate—run(src, comp) / * 可利用メモリを最大限利用して大きな整列を作る * / 307 : FILE * 308 : int { / * init ー buf ( ) がバッフアをロードして事前ソートしている点に注意せよ * / 309 : 310 : S i ze_t 311 : Size t 312 : if (end—of—data) 313 : 314 : return ; memmove(last—item[select], buf—ptrs[rd—count], rsize) ; 315 : i 十十 ) for ( i i くニ rd count; 316 : 317 : w = fwrite(buf—ptrs[i], rsize, (s ize-t) 1 , tmp—filesCselect]) ; 318 : if (w ! ニ 1 ) 319 : serror (WRI TE_ERR) ; 320 : 321 : fflush(tmp_fi les[select]) ; 322 : i く last_minus_one; i 十十 ) for (i dummy_run_count[i] = mn + actual—run count[i + 1 ] actual_run_count[i] ; actual run countCi] = mn + actual run—countCi + 1 ] ; 実際の整列数 = 理想 , 女ミー整列 * / / * = その理想に達するために前のレベルに加える数 * / / * 各整列に対して選ぶ * / d00 ↓ー 000 ー 0000t 0100t ] ー select / * 緊急項目のセーブ用パッファ * / i 十十 ) ソーティングの秘密主義を終わらせよう 35

4. 月刊 C MAGAZINE 1991年11月号

きて、す。 S, LOCATE は問題ありませんが , 関数 CO 確実にエラーとなります。マクロて、ある CL まわないのて、すが , 関数が 2 度以上現れると 同じ定義のマクロは , 何度出現してもか 60 C MAGAZINE 1991 11 LOCATE に注目しましよう。引数 x , y て、 引数型不一致時の対策 LOR が問題となります。 指定された位置にカーソルを移動するマク ヘッダファイルだけで 実現するライプラリ 一般的なヘッダファイルの設計方法はも うわかりましたね。ヘッダファイルて、思い 出すのは「マクロ」て、す。最初のほうて、説明 したように , ライプラリは関数だけを意味 するのて、なく , マクロも立派なライプラリ て、す。 工スケープシーケンスを用いた画面制御 を行うライプラリを集めたヘッダファイル が List 11 て、す。ここて、は画面クリアを行う CLS, カーソル位置設定を行う LOCATE, 表示色の設定を行う COLOR の三つを定義し ています。 なお工スケープシーケンスは , に小 した以外にも数多くの機能があります。 M S-DOS のマニュアルなどを参考にしてくだ こんなに短いへッダファイルて、すが , 多 くの問題点を含んて、います。 正しく記述すると List 12 のようになりま す。それて、は , 問題点をひとつひとつ見て いきましよう。 多重定義対策の欠如 すて、に説明したように , ヘッダファイル は 2 回以上インクルードされても , 多重定義 などのエラーが発生しないように設計すへ 画面制御 15 : } 16 : } LOCATE ( 5 , 1 の ; ロて、す。たとえば , なりません。 LOCATE の場合は , 引数は int 型て、なければ はマクロの長所て、あるといえます。しかし ような型の引数も受けつけられます。これ て、す。互いに比較て、きる型て、あれば , どの たつの引数の大きいほうの値を返すマクロ たとえば List 2 て、示したマクロ maxof はふ ないのはご存じて、すね。 型が異なる場合 , printf 関数が正しく動作し g 型て、す。変換すべき型指定と実際の引数の 最初の引数は double 型 , 2 番目の引数は 1 。 n のように呼び出すとどうなるて、しようか。 LOCATE(IO. 5, 20L ) ; のように呼び出します。さてここて、 , 13 LiSt ティレクトリ表示 ( MS / QC ) struct find_t buf; i nt fl : int main(void) く stdiO. h 〉 く dos. h 〉 1 : #include 「関数形式のマクロて、は型チェックはて、き ない」などと諦めてはいけません。 C 言語に は強力なキャストがありますから , それを 使わない手はありません。 List 12 のように 適当なキャストを行えばよいことになりま す。 これによって , 与えられた引数が int 型て、 あればそのまま printf に渡され ,int 型に変換 て、きる long や double などの型て、あれば変換 された値が渡されます。 構造体などの int 型には変換不能の型て、あ れば , コンパイラはエラーメッセージを出 すて、しよう。 また CLS, LOCATE 自身は値を返す必要 がないのて、 , void て、キャストしていること にも注意してください 3 : 4 : 6 : 7 : 9 : 12 : 14 : while (!flag) { fl 明 = —dos—findfirst("*. * ” , —A—NORMAL, &buf) : return( の ; flag = —dos—findnext(&buf) : printf("Xs#n ” , buf. name) : List 14 ティレクトリ表示 ( Tu 「 bo ) return( の ; flag = findnext(&buf) ; printf("Xs#n", buf. ff_name) : while (!flag) { fl ま findfirst("*. * " , &buf, の : buf; struct ffblk flag; int int main(void) く stdio. h 〉 く dos. h 〉 く dir. h> 1 : #inolude 14 : 12 : 8 : 7 : 5 : 4 :

5. 月刊 C MAGAZINE 1991年11月号

00 ライフラリ開発技法 って仕様が違います。 farmalloc や fmallo c などの異なる名前が割り当てられていま す。この機能も , マクロだけて、処理系間の 差異を吸収することがて、きます。 List 17 にヘッダファイルを示します。 L ist 17 のヘッダファイルく lib \ a110C. h > を用 意すれば , TabIe 1 に示したすべての処理系 y : 7 : : 4 , 13 : } : s : 5 : h : 5 : 24 : ) : 43 : } Fig. 16 て、 List 18 のような共通のプログラムを記述 することがて、きるようになります。 ここて、 far;tk インタが登場しましたが , fa インタに関しては , 後て、詳しく説明しま す。 ディレクトリ検索および far ヒープ管理に 関して , ヘッダファイルを用意するだけて、 ティレクトリの表示 ( ビットフィールド ) List 4 : union DIRdate ( 2 : *include く li di 「 . h > 1 : #include <stdiO. h> 多くの処理系て、共通のプログラムが記述て、 きるようになります。ひとつの処理系て、し か通用しないライプラリと , ヘッダファイ ルをひとつインクルードするだけて、多くの 処理系に対応するライプラリのどちらを選 びますか ? ビットフィールド ディレクトリ検索の話に戻りましよう。 List 16 のディレクトリ表示プログラムは , ファイル名のみを表示するのて、 , DIR コマン ドのように , そのファイルのタイムスタン プ ( 日付 ) も表示するように改良しましよう。 Table 7 各処理系で定義済みの識別子 3 : 5 : 6 : 8 : 9 : 10 : 11 : 12 : 14 : 16 : 17 : 19 : 21 : 22 : 23 : 25 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 38 : 40 : 41 : 42 : s truct { 聞 si 馴 : 7. 第 : 4. d : 5 : #else 聞 si 和 d : 5 , 第 #endif 15 : union DIRtine ( S truct ( #if defined(LATTICE) unsigned h : 5 , 第 #else unsigned s : 5 , 第 : 6. #endif uns igned x; 26 : int nain(void) : 6 , t. x : buf.tine; d. x = buf. date; union DIRtine t; union DIRdate d; vhile (lflag) ( ロ粤 DIRfirst("*. . &buf, の : DIR buf ; lnt flag, return(0) : flag = DIRnext (&buf) ; buf. name, d. d. y + 80. は . d. d. d. d, pr intf( ” % -13S % 02d - % 02d - % 02d 知 2d : % 02 壟 n ” 処理系 LAT LSI C5 C6 Powe 「 QC Tu 「 bo t. t. 町 t. し第 ) : 定義済みの識別子 LATTICE LSI C ( 注 ) ( とくになし ) MSC VER POWER C QC TURBOC ( 注 ) LSI C ー 86 の識別子 L 引 C は処理系定義召ょ いが , ユーサが定義することが推奨されている 日付 / 時間の内部表現とビットフィールド 年 (y) 時 (h) 月 (m) 分 (m) 日 (d) 秒 (s) unsigned y unsigned m unsigned d unsigned h unsigned m unsigned S : 7 ; 4 . 5 . 5 ; . 5 ; . 5 ; 特集 ライプラリ開発技法 63

6. 月刊 C MAGAZINE 1991年11月号

00 ライフラリ開発技法 そのほかのライプラリ 標準ライプラリおよび準標準的なライプ ラリをなるべく使ったほうがよいことがわ かりました。 こて、もう一度 List 3 のプログラムを考え ましよう。 dfind, dnext 関数は LAT のみが 供給するライプラリて、す。このように短い プログラムて、すが , LAT 以外の処理系て、は 2 : ような事態をどう受け止めるべきて、しよう 私としては , このような可搬性の乏しい 処理系付属のライプラリをあえて使う必要 はないと思います。 ほかの処理系への移植時に必ず問題が発 生します。自分て、うまく設計・作成したほ うがよほど都合のよいものとなります。 なお List 3 のプログラムの問題点に関して は , 後て、詳しく考察します。 コンパイルすることすらて、きません。 TabIe 2 List 2 のマクロの機能概路 TabIe 3 コンバイル結果 qube(x) square(x) minof(a,b) maxof(a,b) マクロ この 機 能 ふたつの引数 a , b の大きいほうの値を得る ふたつの引数 a , b の小さいほうの値を得る x の 3 乗を得る x の 2 乗を得る コンバイラ コンパイル結果 LAT 〇 LSI C5 C6 〇 Powe 「 QC 〇 TC X TC 十十 〇 Fig. 3 ライプラリの分類 ライプラリ AN 規格て定められた標準ライプラリ 自作ライプラリ 標準ライプラリ以外の処理系が供給するライプラリ ティレクトリ表示 ( い T ) List return( の ; flag = dnext(&buf) : printf( ” Xs}n", buf. while (!flag) { flag ニ dfind(&buf, struct FILEINFO buf; flag; int int main(void) く stdio. h 〉 #include く dos. h 〉 1 : #include 14 : 13 : 12 : 10 : 4 : 3 : name) ; ライプラリの自作 今回は「ライプラリの開発技法」を解説す るのが目的て、す。ライプラリをいかに使う のかて、はなく , いかに作るかという話題を 取りあげるべきて、す。 そこて、いよいよ本題に突入します。要す る「いかに作り , いかに保守するか」という ことて、す。 ライプラリ開発のための ウォーミングアップ ライプラリを作成するには , 分割コンパ コラム I ANSI ライププ丿の実現形態 ANSI 規格のライプラリ (offsetof, setjmp, va 系マクロを除く ) は , 必ず関数 として実現されることが要求されます。 すなわち , もしあるライプラリをマクロ て供給するとしても , 処理系は必ず関数 特集ライプラリ開発技法 47 るかどうかを確かめてみましよう。 している処理系て正しくコンパイルて、き かを TabIe 3 に示します。みなさんも使用 グラムを正しくコンパイルて、きるかどう Table 1 に示した処理系が , このプロ return ( の , (putchar) ( ' vn' ) , int main(void) #include く stdio. h > 呼び出されることになります。 呼び出すことはて、きないのて , 関数版が の回りにカッコがあるのて , マクロ版を 次のプログラムを見てください oputchar ています。 版も供給しなければならないことになっ

7. 月刊 C MAGAZINE 1991年11月号

List List 13 のように , Turbo て、記述すると List 14 のようになります。 これらは , ライプラリ関数の関数名 , 引 数の順序 , 構造体の名前 , 構造体のメンバ の名前が異なります。たったの 20 行程度の プログラムが , ライプラリの仕様の差異の ために , ほかの処理系て、はコンパイルする ことすらて、きないというのは間題て、す。 しかし , このライプラリの根本的な設計 は , どの処理系も似たようなものて、すから , C 言語の強力なマクロを使用することによ り , 処理系間の差異を吸収することがて、き ます。 List 15 のヘッダファイルを用意する だけて、 , Table 1 に示したすべての処理系て、 も同じライプラリを使うことがて、きるよう になります。 このマクロを利用すると , ディレクトリ 表示プログラムは List 16 のように記述する ことがて、きるようになります。 ティレクトリ表示 1 : #include く stdio. h 〉 2 : #include く lib}dir. h> 3 : #include く lib*dos. h> 4 : 5 : int main(void) i nt flag; 8 : DIR buf ; 9 : 10 : flag ニ DIRfirst("*. * " , &buf, の : 11 : while (!flag) { 12 : printf("%s}n ” , buf. name) ; 13 : flag = DIRnext(&buf) ; 14 : 15 : return( の : List 17 望洋ライプラリ <lib*alloc. h> より抜粋 1 : #if defined(—STDC—) Ⅱ (LSI-C) #define -Cdecl 2 : 3 : #else #def ine —Cdecl cdecl 4 : 5 : #endif 7 : # if ! defined(—LIB-ALLOC_DEF_) 8 : #define —LIB ALLOC-DEF 9 : 10 : #if defined(—TURBOC_) #include 11 : く a110C. h 〉 12 : #el if defined(LSI-C) Ⅱ (LATTICE) Ⅱ (—ZTC—) #include く dos. h 〉 14 : #else #include く mal 10C. h 〉 16 : #endif 18 : #if defined(—TURBOC—) Ⅱ (LSI-C) Ⅱ (—POWERC) Ⅱ (—ZTC—) farmal 10C (s) #define —farmal loc(s) farcal 10C (), s) #define _farcal loc(), s) 20 : farfree(p) #define —farfree(p) 22 : #else f 皿 110C ( s ) #define _farmal loc(s) 23 : ffree ( 0 ) #define _farfree(p) 24 : #if (_MSC_VER 〉 = 60 の 25 : freal 10C (), s) #define —farreal loc(), s) 26 : #endif 28 : #endif 29 : 30 : #endif 処理系間の差異は , うまくマクロを組 むことによって吸収せよ 処理系の自動判別 List 15 のヘッダファイルて、は各処理系ご とに動作を変更する必要があるため , 処理 系の自動判別を行っています。各処理系て、 定義済みの識別子を TabIe 7 に示します。 【演習 4 】 TabIe 7 を参考にして , 処理系を自動判別 し , 処理系名を表示するプログラムを作 成せよ。 LiSt 18 far ヒープ領域のアクセス 1 : #include く li a110C. h 〉 2 : 3 : int main(void) 5 : int far *x; 6 : = (int far *)—farmaIIoc(100 * sizeof(int)); X 8 : x [ 0 ] ~ x [ 99 ] という配列があるかのように使える 10 : farfree(x) ; 11 : return( の ; 13 : ) fa 「ヒープメモリの 確保・解放 8086 用の処理系て、あれば供給されている far ヒープメモリの確保・解放も処理系によ 62 C MAGAZINE 1991 11

8. 月刊 C MAGAZINE 1991年11月号

①①、一 y 、① ry of 、①羆 An 射 List い項目に出会ったら停止します。そして , この両項目を入れ替えます。この処理を , 両方からのスキャンが中央の比較対象のと ころに到達するまて、続けます。この時点て、 , 配列の左半分は値がすべて比較対象以下て、 , 右半分は比較対象より大きい , という状態 になります。 同じ処理を , 今度は配列の左半分に対し て繰り返し行ない , 後て、右半分に対しても 行ないます。半分 , そのまた半分 , というように最後に左半分が 1 項目になるま て、繰り返していくと , 最終的に左半分は完 全にソートされるのて , 次は右半分を手が けます。右半分も , その半分 , そのまた半 分 , と同じ処理を繰り返して , 最終的にソ ートを完了します。 この処理は , 文章て、読むと複雑そうて、す が , Fig. 1 をよく見れば , 明快にわかると思 います。 このアルゴリズムは , 再帰ルーチンとし て実装するのが , いちばんヒ。ッタリて、す。 分割処理を , 最初は左半分から始めて , 再 帰的にコールすることによって , 最後には 分割サイズが 1 項目に減り , 次は右半分を , 同じ条件を監視しながら処理します。する と , 望みの結果が得られます (List 1 ) 。この やり方て、は , スタックに未処理の半分を積 んて、いきながら , 配列をどんどん小さな部 分へと分割していくのて、す。 この実装には , 問題が二つあります。最 初の問題は , めったに起きない極端な例て、 すが , その結果は重大てす。比較対象とし て選ぶ , 配列の中央の項目の値が , たまた ま全項目の値の平均値だったら , ceil (Iog2n) 回の再帰コールて、 , 、、項目が一つしかない〃 という終了条件に到達します ( 例 : 8 → 4 → 2 → 1 ) 。これならいいのて、すが , て、は , ワー ストケースはどうなるて、しようか ? ワーストケースは , 各分割ごとの比較対 象として , その部分の中の最大値の項目を 選んだときに生じます ( Fig. 2 ) 。この場合に は , 分割回数 ( したがって再帰コールの回数 ) と , ソート対象の項目数とが等しくなりま 1 : / * フ。ロク・ラム : 混成ソート ( クイックソートによる事前ソートを行なう多相マーシ・ソート ) 2 : / * 作者 : ケリー・マクティーナン 3 : / * 日付 : 1990 年 2 月 4 日 4 : / * 使い方 : PSORT レコート・長テ・一タ元ファイル目的ファイル レコート・長一 1 レコート・の長さのハ・イト数 テ・一タ元ファイルーオル・リナルファイル名 目的ファイルーソート済み書き込み先ファイル名 8 : 9 : / * 注記 : ソートチンにはテスト用のト・ライハ・フ。ロク・ラムを読者の便宜のために付けてある。 このテスト用ト・ライハ・はあまり柔軟性がないが、実際には大規模なソフトウェア製品の 10 : 一部として柔軟性のある使い方をすることが狙いである。 11 : 12 : ー 13 : 本コードは Turbo C で実装されている。 14 : * / 15 : 16 : # i fdef _TURBOC 17 : #include く a110C. h> 18 : #else 19 : #include く 110C. h> : 20 : #endif 21 : 22 : #include く stdlib. h> 23 : #include く stdio. h> 24 : #include く string. h> 25 : #include く assert.h> 26 : / * ローカルスタックのサイス・ * / 27 : #define MAX STK 128 / * マーシ・ファイルの最大数 * / 28 : #define TMP FILES 9 / * ファイル用のハ・ツフアのサイス・ * / 29 : #define HEAP_SAVE 8192 (unsigned) 0x7FFE 30 : #define MAX_BUF / * このフ。ロゲラム固有のエラーコート・ * / 31 : #define OPEN_ERR 1 32 : #define 2 READ ERR 33 : #define WRITE_ERR 3 34 : #define ALLOC ERR 4 35 : #define DATA ERR 5 36 : serror() ; 37 : void select_file(); 38 : void generate run() ; 39 : void b_set_rd て ) ; 40 : void b_set-wr() ; 41 : void bflush(); 42 : void buf_free ( ) ; 43 : void fill_buffer() ; 44 : void quick—sort() ; 45 : void bmove() ; 46 : void bfill(); 47 : void buf_init(); 48 : void compare() ; 49 : int 50 : / * 実際のファイル数 51 : static int last file; / * 不変値の毎回計算を避けるため * / 52 : static int last minus_one; / * 理想とする整列数 * / 53 : static int actual—run—countCTMP—FILES] ; 54 : static int dummy_run-countCTMP—FILES] ; / * 理想との差 55 : static int least_runs; / * あるレベルでの整列の数 * / / * 整列を受け取る次のファイル * / 56 : static int select; / * 必要なパス回数 57 : static int level; / * バッフアド I / 0 用変数 * / 58 : static void *last—item[TMP FILES] ; 59 : static void *next; 60 : *tmp_fiIesCTMP FILES] ; 61 : FILE 62 : static char fnamesCTMP—FILÉS] [ 13 ] ; / * 作業用ファイルのファイル名 * / 63 : / * ローカルバッファ用の EOF * / 64 : static int end-data 「 TMP FILESI : 65 : static int end_of data; 66 : 67 : static size_t max_items; / * バッフアに入る最大レコード数 * / / * 事前ソート用の項目数 * / 68 : static size_t num_ptrs; / * リート・ / ライト用ホ。インタ , 最前に読んだ項目 * / 69 : static void *buf, *bs ; / * レコート・を指すホ。インタの配列 * / 70 : static void **buf_ptrs ; rd-count; / * スタティックなリート・カウンタ , 最前に読んだ数 * / 71 : static size_t 72 : static size_t rsize ・ / * レコードのサイズ * / *spostTMP FILES] ; / * 各バッフア内の現在位置 * / 73 : static void *rposCTMPZFILES] ; / * 各バッフアの読み出し位置 * / 74 : static void / * 各バッフアの先頭 * / *top[TMP_FILES] ; 75 : static void / * 次に読む項目 * / *next_i tem [TMP_FI LES] : 76 : static void / * バッフアカウンタ * / 77 : static size_t buf_cntCTMP—FILES] ; 79 : FILE * poly_sort(src, rec_size, comp, name) 32 C MAGAZINE 1 1 11

9. 月刊 C MAGAZINE 1991年11月号

0 を①①、一 y 、① ry of 、① r 羆碧 Li st 3 に元データから , 整列の総数が正確に分か っていなければならないからて、す。この難 点は , 定義を拡大して , 長さゼロの整列が あることにすれば , 容易に克服て、きます。 これを , ヌル整列とか , ダミー整列と呼び ます。 最初に , 実際の整列数を現在のレベルに 必要な理想の整列数に設定します。それか ら , ダミー整列の数を , 前のレベルからの 理想に達するために加えることが必要な整 列の数に , セットします。実際の整列が目 的ファイルに書き込まれると , そのファイ ル用のダミー整列の数を減らします。 終了時には , ダミーの整列の数は , 完璧 な配付のためには足りなかった整列の数に なり , すなわち , 完璧なマージを実現する ためにそのファイルからマージすべき長さ ゼロの整列の数となります。ヌル整列をマ ージすることは , 単に , 1 回のパスて、ファイ ルをスキップし , ダミー整列の数をデクリ メントすることて、す。 整列の数の分布て、考慮すべきもうーっの 点は , レベルが高くなると , 前のレベルの 整列の数と , 新たな理想に達するために必 要な数との差がどんどん大きくなることて、 す。ファイルを選んて、 , そこから数百個の 整列を取り出したところて、データがなくな る , なんてことはごめんて、す。したがって , われわれの目標を達成するためには , 整列 をすべての目的ファイルに亙って少しずつ 配付することが必要て、す。このテクニック は , 整列の水平配付と呼ばれます。整列配 付の具体的な細部は , List 3 の select file( ) 関数に実装されています。 こまて、のところは , データ源のファイ ルから一度に 1 項目ずつ読み込んて、いく , と 仮定しています。この方法は明らかに , 整 列を得る方法として最も効率的て、はありま せん。ランダムな順序のファイルを想定す ると , 次に読む項目が前のより大きい確率 は半分て、す。したがって , 整列の平均長さ は 1.5 ということになります。ーっの解決策 は , 主記憶の空き領域を活用して , クイツ 323 : 324 : 325 : 326 : 328 : 329 : 330 : 331 : 332 : 333 : 334 : 335 : 336 : 337 : 338 : 339 : void fil l_buffer(fp) fp; / * ソースファイルからのソートハ・ツフアにテ・一タを入れる * / 340 : FILE * 341 : 342 : void **p; void *bp; 343 : 344 : int size_t j, cnt, num tO read; 345 : 346 : if (feof(fp)) 347 : 348 : rd_count ニ 0 ; 349 : 350 : return ; 351 : 352 : p ニ buf_ptrs ; 353 : rd count num_to_read = max_items ; / * ハ・ツファ満杯まで * / 354 : i く last minus_one; i 十十 ) for ( i 355 : み境界をまたいでリード * / 356 : = fread(topCi), rsize, num—to read, (p) ; 357 : cnt 358 : rd count + = cnt; ニ topCi); 359 : for ( j j く cnt; 360 : / * ポインタをソートする項目にセット * / 361 : 362 : * p + + ニ bp; (char * ) bp + = rsize; 363 : 364 : if (cnt く max_items) 365 : 366 : break ; if (rd—count + max-items > = num—ptrs) / * 多く読みすぎてはいけない * / 367 : num tO read = num_ptrs ー rd count; 368 : 369 : 370 : 371 : 372 : void b_set_rd(n) 373 : int n; 374 : rewind(tmp-fi lesCn)) ; 375 : bfill(n); 376 : 377 : 378 : 379 : void bfill(n) 380 : int n; 381 : 382 : Size t cnt; 383 : rpos Cn] = spos Cn] = to Cn] ; 384 : if (feof(tmp-filesCn])5 385 : 386 : end dataCn] 387 : 388 : return ; 389 : cnt = fread(topCn], rsize, max—items, t 叩 =filesCnJ) : 390 : if (cnt 391 : 392 : end—data Cn] 393 : 394 : return : 395 : (char * ) + = cnt * rsize; 396 : buf-cnt Cn] 397 : end-dataCnJ 398 : next_item[n] 399 : 400 : 401 : 402 : void b_set wr(n) 403 : int n; fil l_buffer(src) ; --rd count; sw i tch (rd—count) / * 読むべきデータがない * / case ー 1 : end_of_data = 1 ; break ; / * 1 項目を読んだ * / case 0 : break ; default: quick_sort(buf_ptrs, 0 , rd—count, comp) ; break ; next ニ buf—ptrs[0] ; / * バッフアにデータを入れる * / = top Cn] : 36 C MAGAZINE 1991 11

10. 月刊 C MAGAZINE 1991年11月号

Li st 継承の利用 (a)o void d0Sink(Sink* sink9 char* bufY sink くく buf; (b) 、 BeautifulSink mysink(8) : つ : do S i nk (&mys ink, æ"ThiS i s → .Longer 新 a れ 1 ine width•- 仮想関数 ところて、 , C 十十て、はく仮想関数 > と呼ば れる形式のメンバ関数が用意されています。 仮想関数は , 宣言時にⅵ rtu 引というキーワー ドをくつつけて通常のメンバ関数と区別し ます。 仮想関数は , 通常のメンバ関数とまった く同じように書けますし , また , 使うこと がて、きます。ただひとつ違っているのは , そのメンバ関数をクラスへのポインタを介 して呼び出したときの振る舞いて、す。 仮想関数を含むクラスは , 内部に ( 見えな い ) 関数ポインタのテープルを持ちます。 のテープル ( 仮想関数テープルと呼ばれる ) には , ⅵ rtual と宣言されたメンバ関数のア ドレスが収められています。クラスへのポ インタを介して仮想関数が呼び出された場 合 , 通常のメンバ関数呼び出しとは異なり 直接その関数が call されるわけて、はありませ ん。仮想関数テープルを参照して間接的に 呼び出される仕組みになっています。 なんて、そんな回りくどいことをするのか というと , まさに , 先ほど持ちあがったよ うな問題を解決するためなのて、す。 List クラスのインスタンス生成の切り換え 皿 ( 血む argc.ghar** argv) ノ / コマンドライン引数の解析など 。一 / / 基本クラスへのポインタ Sink* ink ; switch ( 引数に応じて ) { case イ美しい流し」の要請 : sink 咢れ BeautifulSink()i 数 ) : - 。 / / BeautifulSink クラスのインスタンスを生成 , 、 - break ; case •r 清潔な流し」の要請 : 第 sink れ e 豊 SanitarySink(F 数 ): / / SanitarySink クラスのインスタンスを生成 break; default: sink = れ e Si 心 ( 桁数 ) : //Sink クラスのインスタンスを生成 。 = / / ごこから先は、 *Sink がどのクラスに属するのか 〃区別する必要はない char bufCBUFSIZ] : while (gets(buf))J *sink くく buf; st 7 のように , どのクラスのインスタンスを コラム protected メンバ 生成するかを切り換えるだけて、よいのて、す。 なんと , 継承を利用すればいいことづくめ Padding( ) は Sink クラスのプライベー すなわち , 先ほどのクラス Sink の定義を次 て、はありませんか。 のように書き換えます。 トメンバにアクセスしなければなりませんが , と , まるて、万能ネギ的アイテムのように class Sink { プライベートメンバは , そのクラスのメンバ 述べましたが , 肝心なことを確認しておか p 「 otected : 〃派生クラスからアクセス 関数 ( あるいはフレンド関数 / フレンドクラス ) なくてはいけません。 doSink の例て、いく 〃できるようにする 以外からアクセスできないのが身上です。た と , doSink 関数は sink- > operator < < ( ) とえ派生クラスでもアクセスは許されません。 を呼び出し , さらにその中て、 sink->paddi このようにすれば , 必要なメンバが派生ク 親子の間柄なんだから , それくらい融通をき ng ( ) が呼び出されます。ここて、 , はたして ラスからアクセスできるようになり , スムー かせてくれたっていいじゃないか , といって : padding ( ) 期待どおりに BeautifulSink : も通りません。そのくせ友達には覗かせると ズに Sink を継承することができます。 が呼び出されてくれるのて、しようか ? 答 何でもかんでも派生クラスに公開すればよ いうのだから , なんだか「大人の話だ , 子供は えは否て、す。 外で遊んでなさい」という調子で悔しいかきり いというものでもありません。子供にスネを doSink( ) に引数が渡された時点て、 , Sin かじられすきては大変ですし , 子供は子供で です。 k へのポインタに型変換されていますから , 傍若無人に育ってしまいます。親としての体 その融通をきかせるのが , キーワード p 「 0 : padding ( ) 呼び出されるのは当然 Sink : 面を保ちつつ , 子供をりつばに育てあける。 tected です。 P 「 otected 部は , 派生クラ にほかなりません。あれもこれも , 全部ぬか そうありたいものです。 スにだけアクセスを許した p 「ⅳ ate 部である 喜びだったわけてす。せつかくの苦労も水 と考えることができます。 の泡てすね。 140 C MAGAZINE 1991 11