List d. c C. C 1 : #include く stdiO. h 〉 2 : #include く stdlib. h> /*rand() のため * / 3 : #include く time. h> /*time() のため * / 4 : 5 : char *strC][4]={ { " じいちゃんが " " ぐうぐう " , " いびきをかいたので " , " ねむれなかった . 6 : { ”パイクが ". " ビンビン " , " 走ってきたので " , " かっこよかった . " } , 7 : { " ばあちゃんが " , " 寝ているじいちゃんを” , ”ふんづけたので " を”おかしかった . 8 : { " ぼくが” , ”早起きして” , ”散歩したので " , " 気持ちよかった . ” }. 9 : { " 夜の料理が " , " スーパーミラクル " , " 豪華だったので " , " おいしかった . " } , { " 文を " , " 作るのが” , ”面倒なので” , ”手抜きした . ” } , {NUL し NUL し NULL, NULL}, 12 : 15 : VOi d syaberu(char *s) printf( ” %s ” ,s); 20 : main() int max; / * 発言データの数 * / 22 : 23 : i nt i : 24 : for (max=0;strCmax] C0]!=NULL;max + + ): / * 発言データの数を数える * / 25 : puts ( " ー伊香保の旅の出来事ー " ) : 26 : srand((unsigned int)time(NUL い ): / * 乱数を初期化 . 時間を乱数のたねにする * / 27 : 28 : while(l) { 29 : for ( i = 0 : i く 4 ; i + + ) { 30 : syaberu(str[rand()%max] [i]) ; 32 : putchar('%n'); 33 : 34 : 1 : #include く stdiO. h> 2 : #include く stdlib. h> /*rand() のため * / 3 : #include く time. h> /*time() のため * / 4 : 5 : char *strC) [ 4 ト { { " じいちゃんが " , " ぐうぐう " , " いびきをかいたので " , " ねむれなかった . " } , 6 : トばあちゃんが " , " 寝てるじいちゃんを " , " ふんづけたのでä', " おかしか。た . 7 : 8 : " ぼくが " , " 早起きして " , " 散歩したので " , " 気持ちよかった . " } , 9 : " 夜の料理が " , " スーパーミラクル " , ' 豪華だったので " を " おいしかった . " } , " 文を " , " 作るのが " , " 面倒なので " , " 手抜きした . " } , NULL, NULL, NULL, NULL}, 15 : void syaberu(char **s) / * ここが d. c と違っているところです . * / printf("%s",*s); 18 : } 20 : main() int max; / * 発言データの数 * / 22 : 23 : for (max=O; *strCmax] ! : N 乢し max + + ) : 25 : 26 : puts ( " ー伊香保の旅の出来事 -- " ) : srand((unsigned int)time(NULL)); / * 乱数を初期化 . 時間を乱数のたねにする * / 28 : 29 : while(l) { 30 : for ( i ニ 0 : i く 4 : i + + ) { syaberu(str[rand()%max] + i ) : 32 : 33 : putchar('\n') : 34 : 35 : / * 発言データの数を数える * / ケてみせたのは , どうやら『この手紙が本 に載るのて、読者の心をゆさぶってやろう』 という策略だったようだ。 ところて、 , 今月は吉夫君のプログラムに 勘違いがあるようだが , 皆さんは見つける ことがて、きるだろうか。 【追伸 ( 丹羽信夫より ) 】 皆さん , ロバート氏との勉強会に参加し ませんか ? 氏の勉強の助けとなる教材プ ログラム ( 迷走プログラム ? ) に , 楽しい解 説をつけてぜひ送ってください。応募作は 付録ディスクまたは誌面にて可能なかぎり 紹介し , 掲載作には薄謝を進呈いたします。 送り先 : C マガジン編集部 「迷走プログラミング係』 または , ノヾソコン通信 NIFTY-Serve PFD01142 ( 丹羽信夫 ) までお願い します。 かならず , 本名と住所をお知らせください。 なお , 応募作には手を入れる場合があり ますのでご了承ください。 ろと見ているばかりでした。無理にその場 から引っ張ってきましたが , じいちゃんは 佐々木吉夫 イヤそうでした。 追伸 : 伊香保プログラム改造型を送ります。 b. c です。文章の中身も組み換えるよ うにしてみました。おふざけです。 文字配列の個数をかぞえるようにし たので配列のデータの個数を気楽に 増やすことができます。 NULL という のはデータ最後のしるしのために入 れておきました。これでも , いちお うは工夫しているんですよ。 数日後今度は吉夫君だけの手紙が届いた。 丹羽さん。伊香保プログラム , あまりお もしろくなかったかもしれませんけど , ほ は学校の勉強も不得意ですし。考えるのが くなりにまた改造してみました。 c. c です。 佐々木吉夫 苦手です。 d. c でも同様に動くのですが , 違いがよくわ その後 , 町て、ロバート氏と出会ったが , かりません。前のと同じく文字列表示のと 変わりなく元気なように見えた。 どうやら ころを別の関数として追いやったのがちょ 心配無用のようた。ただ , 「楽しみにしてい っとぜいたくな特徴ですが , ここで char * た C マガ読者の皆さんからの迷走プログラム * s などとして引数を受けているところがわ が今回 1 本も応募されなかった」ということ からず苦労しました。ポインタは中学生の てちょっとさみしそうだった。吉夫君にポ ほくには荷が重いのかもしれません。ばく ノ ' 丹羽信夫の迷走プログラミング 169
三一三ミ 践 C ログラミング とになります。 なお , malloc( ) の返す番地は機械や条件 により変わるのて , Fig. 1 のように番地を書 き込む代わりに , Fig. 2 のように「指す」関係 を矢印て、表すにとどめるのがふつうて、す。 , こまて、の内容は , 紙と鉛筆て、アルゴリ ズムをたどって , よく理解してください プグラムの改良 Fig. 2 Fig. 1 ( d ) を図式的に描いたもの first List 2 にはいろいろな問題点があります。 ストの最後の要素の next 部は常に NULL て、 るルーチン show ( ) て、使われています。 まず , 入力のエラーチェックをまったく行 す。 て、は , リストの先頭 first から始めて , NUL っていません。たとえば main ( ) の この「最後の次は NULL 」という便利な性 do { L になるまて、 next の鎖をたどっていきます。 質は , 26 行目から始まる全データを表示す 登録と逆の順て、表示されるこ したがって , 〔プログラム〕 #include く stdiO.h> # i nc 1 ude く s t 「 i ng . h> #def ine HASHSIZE 63 s t 「 uc t s 「 ec { struct srec *next; i n t p 「 i ce : Char レコード NULL next 560 prlCe name hashtab 0 1 NULL I N K - R X \ 0 void dprice(char * を struct srec * [ ] ) : unsigned hash(char * ) : struct S 「 00 *search(struct srec * を char * ) : / * 商品名 sname を検索し , その単価を表示する * / VOid dprice(char *snat1W struct S 「合 0 *hashtabC])! unsigned i : struct srec * S 「 00P : s 「・ CP = search(hashtab い ], ) : if (srecp ! MJLL) pr i n t f ( ・ツツ記 \n ・ , snzne を ・ 1 se printf('%s tOT F059 \ れ・ , ー ) ・ NULL 155 198 280 1 \ 0 C L - L 1 P E N - M 1 6 \ 0 T A P E / 3 R 4 0 \ 0 62 NULL 580 120 / * 文字列 s のハッシュ値を返す関数 * / unsigned hash(char *s) uns i gned ー wh ⅱ 0 ( *s ! = ハ 0 つ return ー % HASHSIZE: 1 N K - R W\O N 0 T E - A S \ 0 図商品名・単価のデータ構造 ( 2 ) 同一商品名をもっレコードは , 複数個存在しない。 ( 3 ) 表示の形式は“商品名 = 単価”とする。 例 : PEN-M16 = 1 商品名がリスト中に見つからない場合は , " 商品名 NOT FOUND" と表示する。 〔設問〕プ。グラム中の = を埋めて , プログラムを完成せよ。 ただしアドレスを表すときに , & 演算子は用いないこと。 ( 5 ) / * 0 が指すレコードのリストから商品名 s のレコードを探す関数 * / struct srec *search(struct srec *p, char *s) wh i 1 e ( P = return p; 実践 c プログラミング入門 95
, プロクラミンク熨場一 Dr. 望洋の 配列による線形リスト LiSt ないが , ここて、配列を使う線形リストを紹 介しよう ( もちろん , 先に示した線形リスト と同様に , 主記憶上にデータを展開て、きな いほどデータ数が多ければ , この方法は使 ・・・ ) 。もちろん , 配列といっても えないカ・ data [100] ; node のように , 固定した大きさの配列て、はなく , node * ptr ; calloc(100, sizeof(node) ) ・ ptr と , 動的に確保する配列て、ある ( 厳密には『配 列』と呼ぶわけにはいかなしが ) 。配列 を使うことにより , 【利点 2 】が損なわれる ように思える。なぜならば , コンパイルす る時点て、データ数がわかる必要はないが , 配列を確保する時点 ~ すなわち具体的なデ ータ処理を開始する時点て、 , データ数を知 る必要があるからだ。しかし , データ数が プログラムの実行とともに動的に変化する ということは , あまりない。たとえばファ イルからの入力を考えても , 取りあえずフ ァイルを先頭からなぞっていき , データ数 だけをカウントするといった前処理を行え ば , データ数 ( もしくはその上限値 ) を ( ほ ば ) 決定て、きるはずて、ある。したがって , こて、は , それほどプログラムの柔軟性は落 ちないと考えてよいだろう。 配列による線形リス 線形リストは , 概念的には Fig. 1 に示した ような構造を持つが , 各要素を格納する領 域を動的に確保しなければならないという 規定はない。配列によって線形リストを実 現すると , Fig. 7 のようになる。すなわち , 次の要素を指すポインタが , いわゆるポイ ンタ変数て、はなく , 次の要素が格納されて いる位置 = 配列の添え字となっているだけ て、ある。もちろん , t 叩には , 先頭の要素の 添え字が格納されるし , リストの最後の要 素のポインタは , 次に要素がないことを示 すために , 負の値 ~ ここては一 1 ~ を用いる ことにする。 このように配列を利用して実現した線形 く stdio. れ > く stdlib. h> く string. h> #include #include #include #define NuIl typedef int typedef enum { Term, lnsert, Append, Delete, Print, Clear } menu; typedef struct { int no; nameC10] : Char lndex next; lndex Dnext; } node, typedef struct { / * リスト本体 ( 配列 ) * / node / * リストの先頭要素のインデックス * / lndex tOP, / * 利用レコードの最大インデックス * / lndex max; lndex / * 削除リストの先頭要素のインデックス * / deleted; (list->t 叩 ) #define T 叩 / * リストの先頭 * / #define Second (list->n[Top]. next) / * リストの 2 番目 * / #define Next(x) (list->n[(x)]. next) / * x レコードの次の要素 * / ー挿入すべきレコードのインデックスを返す - int GetIndex(Iist *list) if (list->deleted ニ Nu Ⅱ ) return ( 十十 (list->max)) : se { lndex rec list->deleted, list->n[rec]. Dnext; list->deleted return (rec) : / * ヌルインデックス * / / * インデックス型 * / lndex; ー要素 - / * 番号 * / / * 名前 * / / * 次の要素のインデックス * / / * 削除リストボインタ * / / * 削除レコードがない場合 * / ー指定したレコードを削除リストに登録する一 v0id'DeleteIndex(Iist *list, lndex idx) if (list->deleted = Null) { list->deleted = idx; list->n[idx]. Dnext = Null; } e lse { lndex ptr list->deleted; list->deleted = i dx : list->n[idx]. Dnext = ptr, / * 削除レコードがない場合 * / ー要素の各メンパに値を設定 - void SetN0de(node *x, int no, char *name, int next) x—>no x->next next ; strcpy(x->name, name) : ー先頭に要素を挿入 - VOid InsertNode(list *list, int no, char *name) lndex ptr = Top; TOP = GetIndex(list); SetNode(&list->n[Top], no, name, ptr) : - 末尾に要素を追加 - -- * / void AppendNode(Iist *list, int no, char *name) Dr. 望洋のプログラミング道場 125
」切ロ 画・かなっス契 素らしし ) ソ 7 ト よーし丈々的に 売リ出ど でまね 践 C プログラミング 2 ご、さ ? もく し ) らんいらん JS P 7 。ロテクト を見もん / ユニットを使 1 も 余訐ま経を コ一も防止 使うにちない / しましう。 ( * ( ( * first). next) ). next 4 ~ 8 行目に定義されている struct srec とい malloc( ・・・ ) ; う構造体て、す。これについてはすて、に述べ のように数珠つなぎにて、きます。実際には たとおりて、す。 もう少しスマートな方法て、数珠つなぎをす 10 行目て、は , この struct srec 型の番地を るのて、すが , 詳しくはあとて、解説すること 入れるポインタ変数 first を定義し , これに初 にします。 期値として NULL を与えています。 NULL このような「次の要素の格納番地を前の要 とは「ゼロ番地」のことて、 , 工ラー ( メモリ不 素に持たせる」というアイデアて、作られたデ 足 ) のとき malloc ( ) が返す値て、す。実際の構 ータ構造を一般に「リスト」といいます。リ 造体の番地として使われることは絶対ない のて、 , 「まだメモリが割振られていない」と ストといえばプログラムをプリントアウト したものの意味にもなりますが , ここて、い いう状態を示す値としてよく使われます。 うデータ構造のリストはまったく意味が違 実際には , 関数の外側て、定義した変数 ( 関数 います。 の内側て、 static をつけて宣言した変数も含め リストは , データベースソフトには不可 て ) は , 何もしなくても 0 ( ポインタなら NU 欠なデータ構造て、す。また , 人工知能の分 (L) に初期化されることになっていますの 野なども , 知識のデータベースの操作が基 て、 , この「 = NULL 」は余計て、す。 本となりますのて、 , やはりリストを扱いま この first はリストの先頭の要素を指すため す。そもそも人工知能の分野て、もっとも古 に使います。 くから使われている言語 Lisp ( リスプ ) は「リ 12 行目からが新しいデータを登録するル ストプロセッサ」の意て、す。 ーチン ( 関数 ) て、す。 こて、便利な略記法を覚えておきましょ こて、はまず 14 行目て、 struct srec を指す う。構造体て、は ポインタ p を作っています。 ( * p). next 次に 16 行目て malloc ( ) を呼び出すことに という組み合わせをよく使うのて、 , これを よってメモリをシステムからもらい , その 先頭番地をポインタ p に代入しています。 p- > next と書いてよいことになっています。ー > を矢 の 16 行目て、 LSI C ー 86 は 印に見立てれば , いかにも p の指すものの中 Warning . p' used before set の要素 next という感じが出ていると思いま という警告を発しますが , 本来これは不要 せんか ? な警告て、す。無視してください て、はさっそく , 商品データベースソフト 先ほどもいいましたが , malloc ( ) はエラ を作ってみましよう。 ごく簡単なものは Li ー ( メモリ不足 ) のとき NULL という値を返 st 2 のようになります。 します。 17 行目て、は , malloc ( ) の返した値 p が NULL< ないかどうか調べ , もし NUL Lis = 2 の解説 L て、あれば何もせずに呼び出し先の main ( ) に戻るようにしています。 ま imain ( ) から見ていきま 無事にメモリが獲得て、きたら , 20 行目て、 しよ - フ。これ まず品名を尋ねます。キーポードから入力 は , 整数 1 ~ 3 を入力し , 1 ・・・・・・データの登録 した品名は 2 ・・・・・・全データの列挙 p->name すなわち (*p). name 3 ・・・・・・終了 に入ります。これは「ポインタ p の指す構造 体の中の配列 name [ ] の先頭番地」という意 という条件分岐をするだけて、す。 このプログラムて使う基本データ構造は 味てしたね。 そして - らわああ こ oP やられた から JSP7 デクト ュニ、、 / トを使みう , てミ 1 のにフ せ るたけ“ ( ら こ 0 P 0 ーと、し ) ) こしなし ) よウに 小型軽量 / JSPNOTE NEC ラップトップノヾソコン ノートブックパソコン 対応 ユニバーサルプロテクト ・プリンターポートタイプ ・ボードタイプ ・パソコン用アプリケーションソフトの著作権、使用 権を保護するためのハードプロテクトて、す。 ・ソフトウェアユーザのバックアップコヒ。ーは自由て、す。 本器はソフトウェア製作・販売元を対象とした製品です。 問合せ 0482-6 トロ 041 代 - 一十条電子株式会社 〒 333 埼玉県川口市小谷場Ⅱ幻 く資料請求番号 093 〉 実践 c プログラミング入門 93 JSP BASE UNIT
ログラミング List 4 ば firstC1) に登録し・・・・・などとすれば , ひ とつひとつのリストが短くなるのて、 , 検索 が高速化て、きます。 しかし , 商品名の頭 1 文字て、分類するのて、 は , リストごとの長さに差が生じやすい とがわかります。 もっとよい方法はいろいろありますが , たとえば商品名の全文字コードを合計して , それを適当な定数て、割った余りによって分 類するだけて、も , すいぶんよくなります。 つまり , の値て、分類するのて、す ( % は C 言語て、割り算 の余りを求める演算子て、したね ) 。 文字列を数値に変換する関数を一般にハ ッシュ関数といいます。 ハッシュ (hash) はハヤシライスの語源と もいわれ , 切り刻んて、ごたまぜにすること て、す。 上の式はもっとも簡単なハッシュ関数の ひとって、す。 上の割り算の定数は 2 の倍数て、ない適当な 数とします。たとえば 63 とすると , リスト の先頭のポインタは first [0] , first [ 1 ] , first [ 62 ] の 63 個が必要て、す。このようなハッシュ関 数とともに使う配列をハッシュ表 (hash ta ble) ということがあります。そこて、 , List 4 て、は first [ ] の代わりに hashtab [ ] とい う名前にしました。 このようなたくさんの改良を加えた List 4 をご賞味ください return (int)x; / * long を i nt にして返す * / / * ハッシュ関数 * / 29 : 30 . } 32 : unsigned hash(char *s) 33 : { 34 : unsigned i 35 : while ()s ! ニ 36 : 37 : i 十ニ * S 十十 ; 38 : return i % HASHSIZE; 39 : } 40 : void touroku(void) 42 : unsigned i; 43 : struct srec *p; char sname[128]; 44 . 45 : 46 : 48 : 49 : 50 : 52 : 53 : 54 : 55 : 56 : 57 : 58 : 59 : 62 : } 63 : 64 : struct srec *search(struct srec *p, char *S) while ()p ! = NULL) & & (strcmp(p->name, 67 : p->next; 68 : return p; 70 : 71 : void kensaku(void) 72 : { 73 : unsigned i; 74 : struct srec *srecp; char snameC128]; 75 : 76 : printf( ”品名 ? " ) : 77 : if (scanf("%127s%*CA%n] ” exit(EXIT_FAILURE); hash(sname) : 80 : = search(hashtab[i], sname) ; srecp if (srecp ! = NULL) 82 : printf("%s は %d 円です \ n " , sname, 83 : srecp->price) ; 84 : printf("%s は登録されていません \ n ” 85 : sname) ; 86 : } int main() 88 . 90 . 92 : 93 : 95 : 96 . / * i を HASHS I ZE で割った余り * / / * 登録 * / ニ malloc(sizeof *p); P = NULL) { printf( ”メモリ不足 %n"); printf(" 品名 ? " ) ; if (scanf("%127s%*CA%n]" sname) ! = exit(EXIT_FAILURE); = malloc(strlen(sname) + 1 ) ; p->name if ()- 〉 name ニ NULL) { printf(" メモリ不足 %n"); free(p) : strcpy(p->name, sname) : = getint(" 値段 ? " ) ; p->price hash(p->name) ; ニ hashtab[i]; p->next hashtab[i] return; return; / * 文字列をコピー * / / * 検索 * / sname) ! ニ e ー se 情報処理試験 C 言語対策講座 て、 , 昨秋行われた第 2 種情報処理技術者試 験の史上初の C 言語の出題のうち , 前回解説 しなかった問 18 て、すが・・・ 問題を見ていただければわかるように 今回解説した List 4 が解答になってしまいま した。復習のため , List 4 を見ないて、解いて ください ⅶⅱ e ( I) { switch (getint("(l) 登録 ( 2 ) 検索 case 1 : touroku(); break; kensaku() : case 2 : break; case 3 : return EXIT_SUCCESS; 98 C MAGAZINE 1993 2
ープロクラミンク熨場 Dr. 望洋の て、ある。 なお実際には , 未使用領域を管理するた めの情報などもあり , もっと複雑な管理が なされている。 さて , List 1 の実現は , く欠点 1 > ひとつひとつの要素のためのメモリを確 保するときオーパヘッドか大きい という欠点があるようだ。 とくに , 要素が 小さく , 要素数が多い場合は , あまり利用 しないほうがよいだろう。場合によっては , 動的に要素を確保するたびに , 内部的には , それより大きいメモリが消費されるかもし れないのだから。 ・線形リストの特徴 最初にも述べたように , 線形リストは List 1 node *ptr ー * top : vhile (ptr->next ! = NUL い ptr ptr->next; = AllocN0de() : ptr->next SetNode(ptr->next, no, name, NUL い ; ー先頭の要素を削除ー VOid DeleteNode(node **top) if (*top ! = NULL) { (*top)->next; node *ptr free()t 叩 ): *top = ptr; ー全ての要素を削除 void Clear い st(node **top) vhile (*top ! ニ NULL) DeleteN0de(top) : 一線形リストを初期化する一 VOid InitList(node **top) - 線形リストの使用を終了する一 VOid Term い st(node **t 叩 ) ClearList(top); / * 省略 * / ーメニュー選択 menu SelectMenu(void) int ch ; 【利点 1 】 要素の挿入・削除などの操作を , 要素の 物理的な移動を伴うことなく実現できる *top ニ NULL; というメリットがある。また , 実行結果か らもわかるように 要素の追加や削除は自由自在て、ある。した がって , 【利点 2 】 未知のテータ数に対しても , 正しく動作 させることができる。すなわち , コンパ イル時 ( プログラム開発時 ) はもちろん , 処理開始時にすら , データ数が既知であ る必要がない ( メモリの許すかぎり ) do { pu t s ( " ( 1 ) 先頭に要素を挿入 ( 2 ) 末尾に要素を追加 " ) : puts("(3) 先頭の要素を削除 ( 4 ) 全ての要素を表示 " ) ; put s ( " ( 5 ) 全ての要素を削除 ( 0 ) 処理終了 " ) : scanf( ” %d ” } vhile ()h く Term Ⅱ ch > Clear); return ((menu) (ch)) : int main(void) ーメイン一 int node InitList(&Iist); menu; * ⅱ s t ; というメリットがある。これは , C 言語の単 純な配列て、は実現て、きないことて、ある。 しかし , Fig. 1 からもわかるように , そも そも線形リストは , 「次のデータを指すポイ ンタ」をたぐっていくことしかて、きないた め , 大石さんも指摘するように く欠点 2 > ランダムな要素のアクセスが不得手であ る node x; swi tch (menu ニ SelectMenu()) { = Read(" 挿入” ): case lnsert: X InsertNode(&list, x. no, x. name) : : Read ( " 追加 " ) : case Append : x AppendNode(&list, x. no, x. name) : case DeIete: DeleteNode(&list) : PrintList(&list); case Print ・ CIearList(&Iist); case Clear } vhile (menu ! : Term); TermList(&Iist); return ( 0 ) : break , break, break, break , break, という大きな欠点がある。 D 「 . 望洋のプログラミング道場 123
C プロラマのための List 1 バイプ起動ユーティリティ (pipemain. c: 抜粋 ) List / * CTR い C 割込無視 int fi gnore-ctrlc / * ハ。イフ。処理ハ・ツフアオ - ハ・フロー int fbufover / * メモリ種別 int memtype = MEMMAIN; / * 入力ハ。イフ。処理ハ・ツフアへのホ。インタ * / char far *prdmem, / * 出力ハ。イフ。処理ハ・ツフアへのホ。インタ * / char far *pvrmem, / * 入力ハ。イフ。処理サイス・ハ・ツフアへのホ。インタ * / unsigned long *prdsiz; / * 出力ハ。イフ。処理サイス・ハ・ツフアへのホ。インタ * / unsigned long *pvrsiz; / * 入力ハ。イフ。処理ハ・ツファ位置 unsigned long lrdpos; / * 出力ハ。イフ。処理ハ・ツファ位置 unsi gned long lvrpos; *p i pemen[ 2 ] : / * ハ。イフ。処理月・ツフアへのホ。インタ ( 固定 ) * / void far unsigned long lpipesiz[ 2 ] / * ハ。イフ。処理ハ・ツフアサイス・ / * ハ。イフ。処理ハ・ツフアサイス・ unsigned long lpipemax = 円 PESI ZE , void dos3f( struct sirstk far * ). d0S40 ( struct sirstk far * ) : void interrupt $ifndef __TURBOC_ far $endif ne 町 21 ( struct sirstk sirarg ) svi tch ( s i rarg. ax > > 8 ) { case 0X25 if ( fignore_ctrlc & & ( sirarg. ax & 0xff ) break, -chain-intr( 引 d -21 ) : break, case 0X3f i f ( s i rarg. bx ! : FD 燼 Ⅱ prdmem - : NULL ) -chain-intr( 引 d -21 ) ; d0S3f( &sirarg ) ; break, case 0X40 if ( sirarg. bx ! : FDOUT Ⅱ pvrmem ニ NULL ) -chain-intr( 引 d ー 21 ) : d0S40 ( &sirarg ) : break, default chain-intr( 引 d ー 21 ) : break; 十 ーー 0 0 0 0 0 V) X X 0 = FALSE; : FALSE; / * チェーンするアト・レス #end i f int main( int argc, char *argv[] ) static char iret[] ニ { 0xcf } : char cmdnam[ PATHLEN ] ; char CO 引 ine[ 256 ] ; int indexrd indexvr int count, ret; if ( --argc Ⅱ **argv svi tch ( argv [ 0 ] [ 1 ] ) { case case m / * チェーンするアト・レスをスタックに積む * / ヨ ライトハント・ル 入力 ds:dx : ハ・ツフアのアト・レス cx = 書込みハ・仆数 bx ニファイル・ハント・ル 出力 ax : 書き込んだハ・仆数 void d0S40 ( struct sirstk far *pirarg ) if ( *pvrsiz 十 pirarg->cx 〉 lpipemax ) { ( unsigned ) ( lpipemax - *pvrs i z ) ; pirarg- 〉 ax / * ハ。イフ。処理ハ・ツフアオーハ・加 - fbufover = TRUE; } else / * 書き込んだハ・仆数 pirarg->ax ニ pirarg- 〉 memcpy( MK-FP( FP-SEG( P 響 ) + ( unsigned ) ( lvrpos 〉 > 4 ). FP_OFF( p 田 e 田 ) + ( ( unsigned )lvrpos & 0x0f ) ). MK_FP( pi rarg->ds, pirarg->dx ) , / * ハ。イフ。処理ハ・ツファ書込み pirarg—>ax ) ; / * 出力ハ。イフ。処理サイス・更新 *pvrsiz 十 = pirarg—>ax; / * 出力ハ。イフ。処理ハ・ツファ位置史新 lvrpos 十 : pirarg—>ax. / * キャリ - リセット pirarg- 〉 flags & : Oxfffe• / * retf * / / * IRET / * 起動コマント・名 / * コマンド・ライン / * 配列添字 / * 引数の取得 / * ハ。イフ。処理ハ・ツファ : メイン・メモリ = strtoul( &argv[ 0 ] [ 2 ] , NUL し 10 ) ; / * ハ。イフ。処理ハ・ツフア・サイス・ ( ( lpipemax + 15 ) / 0X10 ) * 0X10 : / * ハ。ラク・ラフ・サイス・に切り上げ ニ MEMMAIN; / * メモリ種別 : メイン・メモリ / * 使用法表示 / * CTR い C 割込無視 ? : T23 ) / * 割込へ・クタ 23h の設定を無視する / * 元の INT 21h ハント・ラを呼び出す / * 標準入力以外 ? / * 入力ハ。イフ。処理なし ? / * 元のは T 21h れント・ラを呼び出す / * リードハンドル lpipemax lpipemax memtype break, default usage() ; break, / * 標準出力以外 ? / * 出力れ。イフ。処理なし ? / * 元の燼 T 21h ハント・ラを呼び出す / * ライトハンドル —argc : 十十 argv; / * 元の INT 21h ハント・ラを呼び出す if ( argc usage() : return( 1 ) : / * 使用法表示 リードハンドル 入力 ds:dx ニハ・ツフアのアト・レス cx : 読込みハ・イト数 bx ニファイル吶ンドル 出力 ax : 読み込んだハ・イト数 VOid dos3f( struct sirstk far *pirarg ) ( lrdPOS 十 pirarg- 〉 CX く = *prdsi Z ) ? pirarg->cx pi rarg->ax / * 読み込んだハ・イト数 memcpy( MK-FP( pirarg->ds, pirarg->dx ). MK_FP( FP_SEG( prdmem ) + ( unsigned ) ( lrdpos > 〉 4 ) , FP_OFF( prdmem ) + ( ( unsigned )lrdpos & 0x0f ) ) , pirarg->ax ) : / * ハ。イフ。処理ハ・ツファ読込み / * 入力ハ。イフ。処理ハ・ツファ読込み位置更新 * / lrdpos 十 : pirarg->ax . pirarg->flags & : Oxfffe; / * キャリーリセット ワ 1 . DOS 3. x 以降で動作させてください . \n ” ) : if ( memtype : MEMMAIN ) { / * メモリ種別 : メイン・メモリ ? i f ( ( P i pemem [ 0 ] : ha Ⅱ OC ( ゆ i pemax, 1 ) ) : NULL Ⅱ ( pipemem[ 1 ] ー halloc( lpipemax. ー ) ) : NULL ) { puts_err( "pipenain ・メモリが足りません . \ 心 ) : return( 1 ) : -dos-setvect( Ⅷ T23 , ( void ( interrupt far * ) ( ) ) iret ) ; / * CTRL+C 割込無視 引 d ー 21 ニ _dos_getvect( T21 ) : / * 割込へ・クタ取得 -dos-setvect( T21 , ne 響 -21 ) : / * 割込へ・クタ設定 for ( cmdnam[ 0 ] --argc, 十十 argv ) { count if ( !strcmp( *argv, pipech ) Ⅱ argc pipemem[ indexvr ] : ニ 0 ? NULL ・ pvrmem argc / * 出力ハ。イフ。処理ハ・ツフアへのホ。インタ prdmem : count : 0 ? NULL pipemen[ indexrd ] , / * 入力ハ。イフ。処理ハ・ツフアへのネ。インタ &lpipesiz[ indexvr ] : / * 出力ハ。イフ。処理サイス・・ハ・ツフアへのホ。インタ : & ゆ i pes i z [ i ndexrd ] , / * 入力ハ。イフ。処理サイス・・ハ・ツフアへのホ。インタ ー p i pes i Z [ i ndexwr ] / * ハ。イフ。処理ハ・ツフア・サイス・リセット lrdpos ニ 0 し / * 入力れ。イフ。処理ハ・ツファ位置リセット * / lvrpos / * 出力ハ。イフ。処理ハ・ツファ位置リセット * / ー spavnl ( P_WAIT, cmdnam, cmdnam, co 引 ine, N 乢し ) ; ret i f ( ret puts_err( ” pipemain コマント・の起動に失敗しました . \ 心 ) ; break, pvrsi Z prdsi Z i f ( argc break , 十十 count; indexrd indexvr; indexvr = indexvr; cmdnam[ 0 ] } else if ( cmdnam[ 0 ] static char *pcomspec comline[ 0 ] $i fdef _TURBOC_ void _chain_intr( void ( interrupt far *inthdl ) ( ) ) / * 入力配列添字 / * 出力配列添字 / * 起動コマント・名リセット ニ NULL; / * 環境変数 COMSPEC へのホ。インタ * / / * コマンド・ライン リセット 新 MS-DOS プログラミング入門 101
ん。たとえば , struct srec { char nameC21] ; int price ; は , ポインタ p が指す構造体 * p のバイト数 sizeof * p とするのが正しい流儀て、す。この p = malloc(sizeof (p) ・ って , 構造体のメモリを確保するには れてしまうかもしれないからて、す。したが 1 バイト余分な領域をコンパイラが勝手に入 ほうがよい機械て、は , name [ 21 ] の直後に の場合 , 整数 price が偶数番地から始まった を調べる式て、す。 と , 1000 個作っておけば , 必要に応じて struct srec * P[IOOO] ; 構造体のポインタを 1 件分のメモリが確保て、きます。あらかじめ さて , このようにすれば 1 回の malloc ( ) て、 とがて、きます。それには , まず struct srec * p のように構造体へのポインタ p を作ってお き , 必要に応じて p = m 訓 oc ( 必要なバイト数 ) ; とします。 構造体のバイト数は必ずしも要素のバイ ト数の足し算て、求められるとはかぎりませ 24 : } 48 : } 体的には , たとえば struct srec { struct srec * next ; int price , char name[21] ; としておくのて、す。この next は struct srec 型の構造体を指すポインタ変数て、す。何だ か struct srec 型の定義が完結しないうちに その内部て、 struct srec 型へのポインタを作 ってしまうというのは , 変な気がするかも LiSt 簡単な商品データベースソフト ( 1 ) しれません。しかし , コンパイラにとって はこれはまったく苦になりません。何を指 すポインタて、あれ , ポインタ型のサイズは 2 バイトとか 4 バイトとかに決まっているの て、 , 単にそのバイト数の領域を確保するだ けて、すから。 これて、 , 最初の要素を指すポインタだけ struct srec * first ; のように作っておけば , あとは first = malloc( ・ (*first). next = malloc( ・・・ ) ・ 1 : 2 : 4 : 10 : 12 : 46 : 45 : 44 : 43 : 42 : 40 : 39 : 37 : 36 : 34 : 33 : 32 : 30 : 29 : 28 : 26 : 25 : 23 : 22 : 20 : 18 : 16 : #include く stdi0. h> #include く stdlib. h 〉 struct srec { struct srec *next; i n t pr i ce : char nameC21]; struct srec *first void touroku(void) struct srec *p, / * 次のデータへのポインタ * / / * 登録 * / / * 先頭のデータ * / / * 品名 * / / * 値段 * / = NULL; P = NULL) { ニ malloc(sizeof *P); printf(" メモリ不足 %n"); return, printf(" 品名 ? ” ) : p->name) ; scanf("%s" scanf("%d" printf(" 値段 ? " ) : f i rs t p- 〉 next f i rs t : p[l] malloc (sizeof * p[0] ) ; malloc(sizeof *p[l] ) ; のようにしてメモリを確保て、きます。 しかし , これて、は , あらかじめ十分な個 数のポインタを準備しておくことが必要て、 す。ポインタもデータ 1 件ごとに増やしてい く方法はないて、しようか。 縮自在のテー構造 よい方法があります。さっきの struct sr ec の中に , 次に確保する番地を入れるため のポインタ変数を仕込んて、おくのて、す。具 void show(void) do { i nt C ; p->next; printf("%-20s %lOd%n" while (p ! : NULL) { P f i rs t : int main() struct srec *p; p->name, p- 〉 price); printf( ” (1) 登録 ( 2 ) 列挙 scanf("%d ” ー 1 ) touroku(); 引 s e i f (c = 2 ) show(); } ⅶⅱ e (c ! ニ 3 ) ; return EXIT-SUCCESS; ( 3 ) 終了 ? ” ) ; 92 C MAGAZINE 1993 2
Li st 1 List 1 static char *msg[] " \ 033 [ 36m ハ。イフ。起動ユーティリティ ハ・ - シ・ヨン 0. 01 \ n ” "Copyright (C) 1992 N. NakashimaY033[mYnYn" " 使用法 : pipemain [ - M くれ・ツフア・サイス・ > ] c 面 1 ! cmd2 ! cmd3 ハ。イフ。処理ハ・ツファ : メイン・メモリ \ n ” register char **pmsg : nsg, vhile ( **pmsg ) puts-err( *pmsg 十十 ) : ex i t ( 1 ) ; ニ NULL ) { i f ( getcmd( cmdnam, *argv ) / * 起動コマント・が見つからない i f ( pcomspec ニ NULL : NULL ) { & & ( PCOmspec ニ getenv( "COMSPEC ” ) ) puts-err( "pipemain : 環境変数 COMSPEC が未定義です . \n" break, strcpy( cmdnam. PCONSPec ) ; / * 起動コマント・名 : COMMAND. COM * / strcpy( comline, strcat( comline, *argv ) : strcat( CO 引 ine. . %nYn ” } se strcat( comline, *argv ) : strcat( comline. Table 3 EMM ファンクション 24 Move Memory Region Move Memory Region ( メモリ領域の移動 ) AX = 5700h DS : : コピー元・コピー先の情報からなる構造体 MOVE SOURCE DEST STRUC REGION LENGTH dd SOURCE MEMORY TYPE db SOURCE HANDLE dw SOURCE 爪 L OFFSET dw SOURCE 爪 L SEG PAGE dw DEST MEMORY TYPE db DEST HANDLE dw DEST INITIAL OFFSET dw DEST 爪 L SEG PAGE dw MOVE SOURCE DEST ENDS INT 67h 返り値 AH : 終了ステータス 次の 4 種類のメモリ領域コピーを行います ・標準メモリ→標準メモリ ・標準メモリ→拡張メモリ ・拡張メモリ→標準メモリ ・拡張メモリ→拡張メモリ DS : の指す構造体は次の要素からなる ・ REGION LENGTH コピーするメモリ領域のバイト数 ・ SOURCE MEMORY TYPE コピー元領域のメモタイプ 0 : コピー元が標準メモリ 1 = コピー元が拡張メモリ ・ SOURCE HANDLE コピー元の拡張メモリ領域の八ンドル番号 コピー元が標準メモリの場合は 00h ・ SOURCE 爪 L OFFSET コピーを開始すコピー元領域内のオフセット コピー元が拡張メモリ領域の場合 0000h ~ 3FFFh コピー元が標準メモリ領域の場合 0000h ~ OFFFFh ・ SOURCE 爪 L SEG PAGE コピーを開始すコピニ元領域内のセグメント または論理ページ番号 コピー元が拡張メモリ領域の場合論理ページ番号 コピー元が標準メモリ領域の場合セグメントアドレス ・ DEST MEMORY TYPE コどー先領域のヌモリタイプ 0 : コピー先が標準メモリ 1 : コピー先が拡張メモリ ・ DEST HANDLE コピ先の拡張メモリ領域の八ンドル番号 コピー先か標準メモリの場合は 00h ・ DEST 爪 L OFFSET コどーを開始するコピー先領域内のオフセット コピー先が拡張メモリ領域の場合 0000h ~ 3FFFh コピー先が標準メモリ領域の場合 0000h ~ OFFFFh ・ DEST 爪 L SEG PAGE コピを開始するゴピー先領域内のセグメント または論理ページ番号 コピー先が拡張メモリ領域の場合論理ページ番号 コピー先が標準メモリ領域の場合セグメントアドレスか / * 割込へ・クタを元に戻す dos-setvect( INT21, 引 d ー幻 ) , / * ハ。イフ。処理ハ・ツファわハ・加ー if ( fbufover ) : ハ。イフ。処理ハ・ツフアがオ - ハ・フロ - しました . \ n " ) . puts-err( p i pema ー n return( ret ) : TURBOC_ $ifdef $def ine ATR-EXEC $else $def ine ATR_EXEC $endif 実行ファイルの検索 っ ! 1 ! っ ! っ ! っ ! っ ! っ ! っ ! っ ! ( FA_RDONLY ー FA_HIDDEN ー FA_SYSTEM ) ( _A_RDON し Y ー _A_HIDDEN ー -A-SYSTEH ) 引 数 . EXE の順に検索する PATH のリストにしたがって . COM. 検索ファイルなし 返り値 NU しし fullpath : 検索ファイルあり char *getcmd( char fullpath[], char srcfile[] ) / * 結果を返すフル・ハ。ス名 / * 検索ファイル名 / * 検索ハ。スへのホ。インタ / * コマント・名 / * 拡張子なしフラク・ char far *ppath, char cmdnam[ PATHLEN ] ; i n t fn oex t . struct find_t dta, strcpy( fullpath, srcfile ) ; if ( !_dos-findfirst( fullpath, ATR-EXEC, &dta ) ) / * 検索ファイルあり return( fullpath ) : strcpy( cmdnam, srcfile ) : ニ N 乢し / * 拡張子なしフラク・ strchr( cndnam. fnoext fullpath[ 0 ] ・ \ 0 第 / * 検索ハ。ス : getenv( "PATH" ) ; ppath if ( ppath - : NU ししⅡ !ppathC 0 ] ) / * 環境変数が見つからない return( NULL ) : char *p, int ch fullpath' vhile ( *ppath & & *ppath ! : ( unsigned char ) ( * P + + ch if ( iskanji( ch ) & & *ppath ) *ppath 十十 : * p 十十 if ( ch い * p 十十 strcpy( p, cmdnam ) . if ( !_dos-findfirst( fullpath, ATR-EXEC. &dta ) ) return( fullpath ) ; / * 検索ファイルあり / * 拡張子なしフラク・ if ( fnoext ) { char *pext ニ fullpath + strlen( fullpath ) ; strcpy( pext, if ( !_dos_findfirst( fullpath. ATR_EXEC. &dta ) ) return( fullpath ) : / * 検索ファイル . COM あり strcpy( pext, 汀 ( !_dos_findfirst( fullpath, ATR_EXEC. &dta ) ) return( fullpath ) : / * 検索ファイル . EXE あり } while ( *ppath + + ) : fullpath[ 0 ] return( NULL ) : : *ppath 十十 ) : / * 検索ファイルが見つからない 返り値 - は以外 : 実際に出力したハ・仆数 int puts-err( char *msg ) 工ラ - / * 文字列標準巧 - 出力 return( vrite( 2 , msg. strlen( msg ) ) ) ; / * 使用法表示 VOid usage( VOid ) 102 C MAGAZINE 1993 2
List 2 リストの例を List 2 に示す。 このプログラムは , List 1 とほば 1 対 1 に対 応させているのて、 , リストをよく見比べれ ば , 大枠は理解て、きるだろう。そこて、 , 削 除レコードの管理についてのみ解説するこ ・削除レコードの管理 線形リストを配列て、実現しているわけだ , ここて、次の間題を考えよう。 【問題】要素を削除すると , 配列に空 きがて、きてしまうのて、はないか ? Fig. 8 を見ていただきたい。 ( a ) の状態て、 は , 要素が四つの状態て、ある。 こて、要素 を追加した場合 , 配列の 5 番目の位置に , そ の要素を追加することになる。ただし , 線 形リストの要素は , 配列の添え字の順に並 んて、いるわけて、はないのて、 , ここて、は「線形 リストの末尾」に要素を追加したとはかぎら ことに注意していただきたい。あくま て、も , 物理的な末尾の位置に要素を追加し たのて、ある。 こて、 , 若干の用語の定義をしよう。線 形リスト中の各データのことを要素という。 もちろん , 要素の物理的な位置関係と , 線 形リスト中の位置関係は異なる。そこて , 配列上ての要素の位置 ~ すなわち添え字の ことをインデックスと呼ぶ ( インデックスは 0 から始まることに注意 ! ! ) 。また , 配列とい う観点から見た各データをレコードと呼ぶ 先の例て、は , 要素を追加した結果 , イン デックス = 4 の位置にレコードが格納された ことになる。 さて , ここて 2 レコードのデータを削除し よう (c) 。削除されたレコードは , 無効なレ ドが削除されているのて、 , delete は 2 という ポインタなのて、ある。また , 各要素は , 削 コードとなるが , それ以降の要素の追加な 値を持つ。 除リストの次の要素を指すための Dnext メン どに際しては , 有効に再利用しなけれはな こまて説明すれば , ピンとくる方もい ヾを持っている。 らない。そこて、導入したのが , list 型中の d らっしやるだろう。削除されたレコード群 さらに , list 型には , 現在配列中のどのレ eleted メンバて、ある。 も線形リスト構造て、管理するのて、ある。 コードまて、を利用しているかを記憶するた (a) や (b) のように , 削除されたレコード こて、は , その線形リストのことを削除リス めに , max メンノヾを用意している。 が存在しなければ , deleted は , Null すなわ トと呼ばう。 Fig. 9 の例て、説明しよう。 ( a ) の状態は , ち一 1 の値を持つ。 ( c ) の状態て、は , 2 レコー deleted は , 削除リストの先頭要素を指す max が 7 て、あり , 1 , 3 , 5 のレコードが削除 if (T 叩 = Nu Ⅱ ) InsertN0de(Iist, no, name); else { lndex ptr ニ TOP, while (Next(ptr) ! : Null) ptr = Next(ptr); Next(ptr) = GetIndex(list); SetN0de(&list->n[Next(ptr)], no, name, NuII); ー先頭の要素を削除ー void DeleteNode(list *list) i f (T 叩 ! ニ Nu Ⅱ ) { lndex ptr Second; DeIeteIndex(list, T 叩 ): T 叩ニ ptr; 力 ー全ての要素を削除 VO i d ロ earL i s t ( ⅱ s t * ⅱ s t ) ⅶⅱ e ( T 叩 ! : Nu 日 ) DeIeteN0de(list) ; 一線形リストを初期化する一 VO i d I n i tL i s t ( 1 i s t * ⅱ s t, i n t s i ze) calloc(size, sizeof(node)); list—>max list->deleted ニ Null; list->n list->top / * 省略 * / ーメイン一 int main(void) i n t 1 i s t menu ; ⅱ s t ; i t し i s t ( & ⅱ s t, 10 の : do { node x; svi tch (menu : SelectMenu()) { ニ Read(" 挿入 "): case lnsert: X InsertN0de(&Iist, x. no, x. name) : case Append : X = Read( ”追加 "): AppendN0de(&list, x. no, x. name) ; case DeIete: DeleteNode(&list) : case Print ・ PrintList(&list); case Clear ・ CIearList(&list); } vhile (menu ! ニ Term) ; TermList(&list); return ( 0 ) : break, break, break, break, break : 126 C MAGAZINE 1993 2