Fig. 1 Tu 「 bo C 十十のクラスライプラリ Object* Association* Sortable* E 「「 0 「 Container Collection* AbstractArray* DoubIeList( 双方向リスト型 ) A 「「 ay ( 配列型 ) SortedArray HashTable B a g— Set Dictionary ( 連想配列型 ) ( 八ッシュテープル型 ) ( 集合型 ) ( 集合型 ) List( リスト型 ) String ( 文字列型 ) BaseTime BaseDate Stack( スタック型 ) Queue ( 待ち行列型 ) Deque ( 両方向待ち行列型 ) Containerlterator ListElement DoubleListElement Arraylterator Doublelterator Li st lte rator 注 1 クラス名の右上に * がついてるものは抽象クラスである 注 2 Bag クラスと Set クラスの違いは Bag クラスが要素の重複を許すのに対し , Set クラスは許さない点である リのリファレンスて、す。 ところて、 , C 言語を知っているプログラマ が , C 十十を学ぶときの障害となるのは , C 十十がオプジェクト指向という概念のうえ に存在していることだと思います。そのた め , いわゆる「習うより慣れろ」という学習 法があまり役に立たず , まずは , 「オプジェ クト指向」という概念をしつかりと理解する 必要があります。 しかし , このオプジェクトという目に見 78 CMAGAZINE 19 10 えないものに対する技法が , なかなか難し く , 理解しにくいことは確かて、す ( いちど理 解すると , 非常に機能が高く , オプジェク ト指向の虜になってしまうのて、すが・・・ また , C 十十には , さなざまな技術 , 機能が 豊富に詰まっているのて , 「群盲 , 象をなて る」のことわざもあるように , やもすると本 質を見失いかねません。その点 , ①の Tretting Started" は , C 十十の本質をきっちり押さ えて書かれているのて , 初心者にはわかり やすいのて、はないて、しようか。 Turbo C 十十 Ver. 1.0 の言語仕様 先にも述べましたが , TurboC 十十 Ver. 1.0 は , AT & TC 十十 Ver. 2.0 の機能を完 全実装しています。 以下に , TurboC 十十 Ver. 1.0 の言語仕 様のうち , C 十十 Ver. 2.0 に関係する部分
ポインタと配列 ( 1 ) mo 第 3 回 C 言語の入門者にとって , もっとも難解な言語機能のひとつにポインタが挙げられる。今回は , ポインタ について , 1 次元配列との関係に限定して説明する。 復習はこんなところて十分だろうか ? (List 1 , 2 参照 ) 。 配列とホインタの違い ・配列名はポインタ定数てある。したが 配列をアクセスする機能 C 言語入門者にとって , ポインタはもっと って配列名には代入てきない も難解な言語機能のひとつに挙げられてい 最初の問題は , C においては配列表記とポ ・ポインタ変数は ( const てないかぎり ) 定 る。ポインタを難解にしている要因のひと インタ表記の混用が可能だという点て、ある。 数てはなく変数てあり , それには代入 っとして , ポインタと配列が密接な関係を こて、配列表記とは a [ i ] のような形式を指し てきる。 もって存在している ( 絡み合っている ている。また , ポインタ表記とは * (a 十 i) ・配列は宣言時に定数式て添え字を指定 ことが挙げられるだろう。しかし , C てプロ といった形式を指している。 しなければならない。配列名が示すオ グラミングを行う場合に , ポインタを避け プジェクトを先頭に , 宣言時に指定し たとえば , 次の断片的なプログラムの記 て通ることはてきない。そこて今回はポイ 述はいずれも正しい ( List 3 , 4 参照 ) 。 た数のオプジェクトが , 配列オプジェ ンタと配列の関係を考察し , 両者の複雑に 関数 f00 て、は配列 a をポインタ表記て、用 クトの生成された時点て確保される。 入り組んだ関係に光を当てることを目標に い , 逆に関数 bar て、はポインタ p を配列表記 それはコンパイル時かもしれないし , する。とりあえず今回は , 1 次元配列に限定 て、使用している。しかしこれらは文法的に 関数の内側て auto 配列として宣言され して話を進める。 2 次元以上の配列とポイン 正しいプログラムて、ある。さらには , 両者 れば , その関数に制御が移った時点 ( ス タの関係は , 回をあらためて取り扱いたい を混在させることも可能てある。つまり次 タックフレームが生成された時点 ) て 初心者によくあるまちがいは , ポインタ のような List 3, と List 4 ' もまた , 文法上は 保されることになる。 と配列を混同してしまうことてある。実際 正しいプログラムてある。 ・ポインタ変数は「指し示す先のオプジ には両者はまったく別のものてある。しか 実際 , 配列の 0 番目の要素を指定するに ェクトの型情報」をもつにすぎない。実 し , C 言語の意味上および表記上て , 両者は は , a [ 0 ] と書くよりも * a と書いたほうが短 際にポインタ変数が指し示す先に正し 非常に紛らわしくも似かよった表現が可能 くなるし , ポインタ p がポイントしている要 くオプジェクトが存在するかどうかは , て、あるために , どうしても誤解する人が出 素の 10 個先の要素を指定するには , * (p + プログラマが保証しなければならない てきてしまう。 10 ) と書くよりも p [ 10 ] と書いたほうが簡潔 また , ポイント先に存在するのが実は 今回は , 配列とポインタとが密接な関係 に表現てきる。このため , このような混在 オプジェクトが複数個並んだ配列なの をもって・いるということを述べるのだが , 記述はしばしば見受けられる ( とくに , 少し か , あるいは単一のオプジェクトなの それに先だって , まず両者は違うものてあ てもタイヒ。ングを省略しようという怠惰な かの区別もプログラマの責任て、管理し るということをはっきりと確認しておこう プログラマのコードては : ー ) ) 。 なければならない きだあきら List List int a [ 100 ] : int foo(void) return a[0] + a [ 10 ] : int a [ 100 ] : int bar(void) 1 よワ朝っ・ 4 ・ L.O 《 0 8 1 ーっ -4 ・ 0 《 0 int *p return *p 十 *(p 十 10 ) : 118 CMAGAZINE 19 10
引 mo の場合にはアドレス値として考えると , + 十 sizeof(a) を評価してみるとよい ( たとえば , ポインタを baz に渡しているにすぎないのて、 p が必ずしも 1 増やしているとは限らない sizeof(a)) : とする ) 。 ある。 printf("%d%n" とに注意されたい そのため baz の側て、 , たとえどんなに配列 sizeof(int*) と同じ値が返されるはずて、あ る。したがって , List 5 および List 5 ' の引数 が一括して渡されてくるかのように記述し 配列形式の宣言 ても , それは意味をなさず , たんに先頭要 宣言は次の形式とまったく同じて、ある ( List さて , 話をややこしくしている問題のふ 素へのポインタが渡されてくるにすぎない 7 参照 ) 。 たつめを考察しよう。それは関数の仮引数 先の baz の引数宣言は , いかにも配列の宣言 これならば , a がポインタて、あることが一 に対する配列形式の宣言て、ある。次の関数 て、あるかのように見えるのだが , 実はたん 目瞭然て、あろう。その場合て、も , 関数 baz の を見てもらいたい (List 5 参照 ) 。 本体て、は a 国と書いてもかまわないというこ なるポインタて、しかないのて、ある。 まず , C て、は配列をそのまま一括して関数 ・関数の仮引数として配列風に宣言して とは最初に述べた。 へ引数て、値渡して、きないということを思い も , 実際にはポインタとして取り扱わ なお , List 5 ' の引数における int a ロと 出してほしい ( 配列を構造体て、包んて、しまえ いった宣言の形式と , 次のような外部リン れる。 ば可能だが・・・・・・ ) 。この baz 関数は , 別なと List 5 は次のように仮引数の配列サイズを ケージをもっ配列の宣言とを混同してはな ころから , List 6 のように呼び出すことにな 省略することがて、きるが , これて、もまった らない (List 8 参照 ) 。 List 8 の a は正真正銘の配列て、ある。これ るのだろう。 く事情は同じて、ある。すなわち , List 5 にお 関数への実引数は式 ( 厳密には「代入式」 ) を extern int * a : と書くと , まったく違う ける a [ N ] の N はほとんど意味はない ( List 5 ' て、ある。したがって , data という配列名が 意味になり , プログラムは正常に動作しな 参照 , なお , 2 次元以上の配列を受け渡す場 己されていれば , それは「配列名は先頭要素 いて、あろう。 合などはサイズの指定が重要になる。それ へのポインタに自動変換される」というルー については回をあらためて述べたい ) 。 ルに従って処理される。 zot の中て、の baz の いずれにせよ , 引数 a は配列て、はない。そ 呼び出しは , 実際には data の先頭要素への れはポインタなのて、ある。 baz の内側て、 1 ロ List ニ baz (data) : total ー 8 100 #define N baz(int a[N]) long 11 「と 4 ・ -. 0 ^. 0 ー 8 0 ) 0 1 ↓ LiSt long i nt i : sum 十十 i ) for (i ニ 0 ; i く N; S u m return sum : baz(int *a) 11 ワ】っ 0 -44 LO 《 0 ー 8 long long i nt i : sum for ( i 0 : i く N : 十 + i ) sum return sum : LiSt baz(int long List extern int a[]; long baz(void) 11 ワ 3 っ 0 ・ L.n れ 0 0 ー 8 0 1 よ List long i nt i : int data CN] : void zot(void) 1 人っなっ・戸 0 ^ 0 sum for ( i ニ 0 : i く N : + + i ) sum sum : return total; long ANSI C : more 121
また , 配列のサイズを指定することによ り , 次のような 2 タイプの初期化を行うこと もて、きます。 char cc [ 4 ] ー "ABC" char cc [ 3 ] ー "ABC 上記のふたつの例て、は , 初期化リストの サイズが同一て、あるにもかかわらず , 配列 のサイズ指定が異なっていますが , どちら の場合にも正常にコンパイルが行われます。 配列サイズとして 4 を指定したものは , 配列 サイズを省略した場合と同じ結果となりま すが , 配列サイズ 3 を指定したものは , 以下 のような宣言と等しくなります。 浮動小数接尾子 文字 文字ェスケーフ。 8 進工スケーフ。 1 6 進工スクーフ。 文字列 文字 文字工スケープ 8 進整数 8 進工スケープ char cc [ 3 ] つまり , 文字定数の末尾に存在するはず の \ O 〃は無視されるわけて、す。 型変換 1 6 進整数 1 6 進工スケープ 文字 文字ェスケーフ。 8 進工スケーフ。 1 6 進工スケーフ。 文字定数 式の評価が行われる際の比較的多くの場 合において , char 型から int 型への暗黙的な 拡張変換が行われます。この変換規則を C 言 語のプログラム風にまとめると以下のよう になります。 代入演算子 算術演算子 演算子 代入演算子 if(C = SignedChar) (signed int)C; else if(sizeof(C)== sizeof(unsigned int) (unsigned int)C ; else くく 52 CMAGAZINE 19 10
も , 列として宣言されているてはないか。どこ にポインタが存在するのか ? この疑間に対する答えは簡単て、ある。 a は 配列名だが , これは先頭要素へのポインタ 定数として扱われるのて、ある。厳密ないい 方をすれば , 式の中に配列名が出現すると , それは自動的に先頭要素へのポインタ定数 に変換されるのてある。つまり , a と書くの と &aCO] と書くのは同じなのてあるにのル ールには 2 点例外がある・・・・・後述 ) 。 & a [ O ] と書けば , これは明らかにポインタ て、ある。なお , 単項演算子の & を「アドレス をとる」と考えている人がいる。これはまち がいとはいえないが , 正しくは「そのオプジ ェクトへのポインタを生成する」と考えたほ うがよいだろう。 この配列名から先頭要素へのポインタへ の自動変換ルールは , おそらく配列を関数 の実引数て、渡す際に自動的にポインタが渡 るように , すなわち実質上の参照呼び ( call byreference) を実現するために考えられた 言語仕様て、あろう。単純て、ありながら実に 効果的な言語仕様て、ある。 C の魅力は , した細かいところがきわめて合理的かっ実 用的にてきているところにもある。 配列表記に関して誤解を恐れずにいって しまうと , 結局 C には配列をアクセスする機 能など存在しないのだということて、ある。 たとえ配列表記 ( a [ i ] ) を使用している場合て、 も , 実は配列名をポインタ定数として取り 扱い , ポインタ形式 ( * (a + i)) としてアク セスされているということになる。 スケーリング こて、話のついて、にスケーリングのこと について触れておく。先ほどから再三述べ ているのだが , ポインタ a に整数 i を加える次 の式は , a がポイントするオプジェクトから i 個先のオプジェクトへのポインタて、ある ( 厳 密には , これが意味をもっためには a と a 十 i が同じ配列の内部のオプジェクト , もしく はその配列の最後のオプジェクトの直後の 仮想的なオプジェクトをポイントしている 必要がある ) 。 &aCi] → & * (a + i) → a 十 i という変形を正しく成立させるために 120 CMAGAZINE 19 10 これは必要な条件て、ある。 また , 加える代わりに減ずることも (a ー i ) てき , その場合には i 個手前のオプジェク トという意味にならなくてはならない れらのことは , たとえ a がどのようなオプジ ェクトへのポインタて、あれ ( void * は除く・・ 後述 ) , 一貫して成立してもらわなければ困 ることになる。 C 言語の文法の表面から少し機械語のレベ ルへと降りてこの主張を解釈してみると , ポインタと整数の演算は単純な加減算ては ないということがわかる。たとえば , 今 a が int へのポインタてあるとしよう。そして , 使用している処理系が int には 16 ビット , す なわち 2 バイトを割り当てているものとしよ う。そうすると , a 十 1 て a の直後の int をポ イントするためには , 実際には a がポイント しているオプジェクトのアドレスの 2 バイト 後をポイントしなければならない。 a 十 2 てあれば , 4 バイト後をポイントする必要が ある。以下同様て、ある。 このことは , ポインタに対する整数の加 減算ては , 実際のアドレス計算においては , 自動的に整数値にオプジェクトの sizeof が掛 けられた値が加減算されているということ を意味している。すなわち , ポインタ p に対 して整数 i を加える次の式の場合 , 単純にアドレスの計算てあると考えると ( ここて、はポインタのビット幅と long のビッ ト幅は等しいと仮定している ) , 次のような 演算が機械語レベルて、行われていることに (long)p 十 sizeof(* p) * i 一般に , この sizeof ( * p ) を掛ける作業を スケーリングなどと呼んて、いる。ポインタ への加減算て、スケーリングが行われること は重要て、あり , いくつかの事実と制約が必 然的に こから生まれることになる。 まずポインタは , 「何へのポインタ」て、あ るかを明確にして宣言されなければならな い。対象としているオプジェクトサイズが わからなければ , その対象オプジェクトの sizeof を取ることがて、きず , したがって , ス ケーリングがてきないからてある。このこ とはまた , 「とくに何へのポインタというわ けてはないポインタ ( 総称ポインタ ) 」という 定義て、ある void * て、はスケーリングを行う ことがて、きないということを意味している。 したがって , void * に対しては加減算を行 うことはてきない ( ただし , GNU の C コンパ イラ gcc などて、は拡張が行われていて , void * に対してはスケーリングを行わないて , そのまま加減算される仕様になっている ) 。 次に , ポインタとポインタを加えるとい うような演算は許されない。あくまて、スケ ーリングされるのは整数に限られる。まし てやポインタに乗除算を行うというような 演算は許されない 一方 , ポインタからポインタを減ずると いうことは , 双方が同じオプジェクト型へ のポインタて、あった場合に限れば可能て、あ り , その場合にはアドレス値の差に対して 逆のスケーリング ( すなわち除算 ) が行われ , 結果として , ひとつのポインタがポイント しているオプジェクトから他方のポインタ がポイントしているオプジェクトの直前ま て、の間に存在するオプジェクト数が得られ る ( ただし , この演算結果が意味をもっため には , ふたつのポインタは同じ配列の内部 のオプジェクト , もしくはその配列の最後 のオプジェクトの直後の仮想的なオプジェ クトをポイントしていなければならない ) 。 なお , ほかの型の変数の場合と同様に ポインタ変数 p に対しても , 次の 4 つの演算 はすべて同じ意味て、ある。 十十 p, p 十十 , p 十 = 1 , p これらの作業は ( p が複雑な式て、現されて いる場合の評価の問題を除けば ) , すべて最 p 十 1 として考えてよい 後に記した P こて , p 十 1 て、はスケーリングが行われ したがって , 上記の 4 つの演算の結果 , p に格納されているアドレス値はすべてオプ ジェクトサイズて、スケールした値だけ増加 することになる。当然ながら減算 ( ーー p, P 1 ) に関しても i は「 1 増やす」ことになるのだが , ポインタ p 同様のことがいえる。 i 猷の変数てあ + +
可能な物理アドレス配列の取得 get ー paa ( ) , ハンドル名のサーチ search_handle( ) を行い ます。 物理アドレス配列 paa は , 先月号のメモリ マップの図 ( ' 90 年 9 月号 ) の各ページフレーム のセグメントアドレスと物理ページ番号か EMS ハンドル名をサーチした結果 ( 見つかっ AH=54H AL=OIH AH=58H AL=00H ングして利用しています。関数 setup( ) は 1 のどちらかをマッビ このプログラムて、は , 物理ページ 0 だけを使 用し , 論理ページ 0 , らなる配列て、 , このアドレスを標準メモリ と同じようにアクセスすればよいわけて、す。 TabIe 2 拡張ファンクションの詳細情報 ファンクション NO. 8 ページマップのセープ コール リターン AH=47H AH=OOH : 正常終了 ( セープされた ) DX=EMM)\ ンドル 80H , 81H, 83H , 84H , 8CH, 8DH . 異常終了 ファンクション NO. 9 ページマップのリストア コール リターン AH=48H 80H , 81H, 83H , 84H , 8EH . 異常終了 AH=OOH : 正常終了 ( リストアされた ) DX=EMW\ ンドル ファンクション No. 20 八ンドル名のセット コール リターン AH=53H AL=OIH DS : S ド八ンドル名へのポインタ 80H , 81H, 83H , 84H , 8FH, AIH : 異常終了 AH=OOH : 正常終了 ( 八ンドル名をセットした ) ファンクション NO. 21 指定の名前をもつ EMS 八ンドルのサーチ コール リターン DS . S ドサーチする八ンドル名 ( 8 ノヾイト ) へのポインタ AH=OOH : 正常終了 ( EMM 八ンドルが見つかった ) DX = 一致した八ンドルの値 80H, 81H, 84H , 8FH, AOH, AIH : 異常終了 ファンクション NO. 25 マッピング可能な物理アドレス配列の取得 コール リターン ES : D ド物理アドレス配列へのポインタ AH=OOH : 正常終了 ( セグメントアドレス配列が見つかった ) 80H , 81H, 83H , 84H , 8FH : 異常終了 CX = 工ントリ数 た場合は OK : 1 , 見つからない場合は NG : 0 ) を戻り値とします。 EMS ハンドル名が見つからなかった場合 は , このプログラムがはじめて起動された ものとし EMS ページの確保 , 名前の設定を 行います ( PDS やシェアウェアなどて、 , EMS メモリ領域の使用状況を見られるものがあ ります。 これらを利用すると , EMS メモリがどの ような名前て、何バイトの領域が確保されて いるのかを調べることがて、きます。例 : PMAP)0 そのあと , 使用する EMSR—ジ (Table 1 ) をマッヒ。ングし , 物理ページ 0 の先頭アドレ のようにして宣言して , * *ems area2 ; Note far Schedule far * *ems areal ・ 配列にしたい場合には , のポインタを設定します。もし , 構造体を スにスケジュールあるいはメモの構造体へ て、ポインタを設定し , たとえばスケジュー CO]. pseg * Ox10000L ) ; ( N Ote far * * ) ( Pa ems area2 ( Pa [0]. pseg * 0xIOOOOL ) ; ( ScheduIe far * * ) ems areal printf( "<memo>%s*n", buf ) : memo, 60 ) : strncpy( buf, ems areal [ 1 ] ー > printf( "<time>%s*n", buf ) : time, IO ) ; strncpy( buf, ems_areal [ 1 ] printf( "<date>%s*n", buf ) ; date, IO ) ; strncpy( buf, ems areal [ 1 ] ルの 2 番目を参照するならば , とすればよいて、しよう。 用してください れるようなモデル ( ラージモデルなど ) を使 うまく動作しません。セグメントが指定さ って , スモールモデルて、コンパイルしても りには strncpy( ) を利用しています。したが 実際に EMS 領域と標準メモリとのやりと 124 CMAGAZINE 19 10
五ロ はしめて学ぶプログラミンク ルを ASC ⅱファイルに限り , テキスト モードでコピーします ( List 8 ) 」 このプログラムて、は 1 行をひとまとまりと して処理をしています。 1 行読んて、は配列 ( こ の場合サイズ 256 ) にしまい , また 1 行ごと書 き出しています。そして最後に何行コヒ。ー をしたかを表示しています。 ちょっとした付加機能てすが , コヒ。ーす ると同時に画面 ( 標準出力 ) にも表示したい ときには , List 8 の主要部分を , List 9 っ 0 っ 0 っ 0 っ 0 fclose(InPile); fcIose(0utFile) : List 1 0 Iist10. c 2 : 4 : #include く stdio. h> 5 : #def i ne N 26 6 : 7 : void main(void) i nt i , n : str2[N + 1 ] : char str1[N 十 1 ] , F I し E * I nF i 1 e, *0utF ⅱ e ; if ((0utFiIe ー fopen("block. tst", fprintf(stderr, open fi le error exit(-l); 0 : i く 26 : i + 十 ) for (i 17 : strl[i] strl [ 26 ] ' \ 0 冖 fwrite((void *)strl, sizeof(strl), (size-t)l, OutFiIe) : 20 : n (int)n); printf(" %d 個のデータを block. tst に書き込みました YnYn", 21 : fcIose(0utFiIe) : 22 : 23 : ニ NULL) { if ((InFile = fopen("block. tst", 24 : open fi le error : block. tstYnYn") : fprintf(stderr, 25 : exit(-l); 26 : InPile); fread((void *)str2, sizeof(strl), (size_t) 1 , 28 : n (int)n); printf(" %d 個のデータを block. tst から読み込みました YnYn" 29 : 30 : str2) : printf ( " 書き出したデータは %s \ n 読み込んだデータは %s YnYn" strl, 32 : fcIose(InFiIe); 33 : 34 : } t e 関数の例 f w r 1 f r e a d 、 count while(fgets(str,256,lnFiIe)!=NULL) { count 十十 fputs(str, OutFile) ; fputs(str, stdout) ; / * この行を追加 * / printf(" % d 行転送しました *n" count) ; ニ NULL) { : ock. tstYnYn") : と書き換えればよいことになります (List 例 4 「プロック入出力関数を考えてみま しよう」 配列や構造体のデータをまとめて ( プロッ クて、 ) 読み書きしてくれるのが ,fread,fw 「 ite 関数てす。配列や構造体をまとめて入出力 て、きるという点て、 , このプロック入出力は 非常に便利て、す。配列要素をひとつひとつ 扱ったり , 構造体メンバごとに出力形態を 変化させることなく入出力可能て、す。 まず fread(fwrite) 関数の引数について説 明しましよう。それぞれの書式を示します (TabIe 4 ) 。 各関数の働きを言葉にすると , fread : ストリームから配列 ( * ptr ) に 大きさ size のデータを n 個まで読み 込む fw 「 ite : 配列から ( * p ①ストリームへ , 大きさ size のデータを n 個まで書き 込む ということてす。読み込んだり , 書き込ん だりするデータをしまってある配列の格納 先は vöid へのポインタて指定します。入出 はじめて学ぶ C プログラミング 139 List 1 1 t e 関数の例その 2 r e a d 、 f w r 1 1 i s t 11. c f 2 : 4 : #include く stdio. h> 5 : #def i ne N 26 6 : 7 : void main(void) i nt i , n : 9 : str2[N + 1 ] : char str1[N + 1 ] , PI し E *InFiIe, *OutFile; = NULL) { if ((0utFi le = fopen("block. tst"• fprintf(stderr, "open file error : block. tstynyn") : exit(-l); for (i = 0 ; i く 26 : i + + ) strl[i] strl [ 26 ] (size-t) (N + 1 ) , 0utFi (e) : = fwrite((void *)strl, sizeof(char)' 20 : n printf(" %d 個のデータを b Ck. tst に書き込みました \ n \ n " , fcIose(0utFi (e) : 22 : 23 : = NU しい { if ((InFile = f 叩 en("block. tst", 24 : fprintf(stderr, "open file error : い ock. tst%nyn") : exit(-l); 26 : (size-t)(N + 1 ) , InFile); fread((void *)str2. sizeof(char), 28 : n (int)n); printf(" %d 個のデータをい ock. tst から読み込みました 29 : 30 : printf ( " 書き出したデータは Xs \ n 読み込んだデータは Xs YnYn", str2) : strl, 0
イルポインタやアクセスモードを変えるこ とにより , 互いに干渉し合うことがて、きま す。 第 2 の注意点が , ファイルアクセスがノヾッ ファリングされる場合・・・・・・ C 言語の stdio のラ イプラリを使用する場合・・・・・・に関して存在 します。 stdio を使ってデータをファイル に書き込むとき , データはバッフアが満杯 になるまて、 , またはバッフアをフラッシュ する関数がコールされるまて、 , あるいはフ めた一般名て、す。 exec 関数の原型は execve としての , プログラム名とそれに渡す引数 ァイルがクローズされるまて、 , メモリ上に て、 , これは int execve(char *path, char * を必要とします。しかしこのデータをユー argv [ ] , char *envp [ ] ) ; という引数形 バッファリングされます。このときはシス ザ入力やファイルから取得するときは , そ 式て、コールされます。 path は実行するプロ れは通常は単一の文字列て、あり , したがっ テムコールの w 「 ite 力すコールされて , ノヾッフ アの内容をファイルに書き出します。しか グラムのパス名 ( ファイル名の前に所在ディ て配列またはリストへとトークン化されな し , バッフアはプロセスの一部なのて、 , プ レクトリ名などのパス名をつけたもの ) て ければなりません。 List 4 に , execs 関数と ロセスの複製時にはそれも複製されていま す。 argv ( 引数の配列 ) は , この新たなプロ いう新しい exec 関数を示しますが , これは コマンドを文字列形式て、受け取るものて、す。 す。 fo 「 k がコールされたときに , バッフア内 グラムに渡す引数の文字列の配列て、す。 envp に書き込み用データがあると , このことに ( 環境ポインタ ) は , name=value という形 execs 関数の環境版 execse と , パス名版 よって予期せざる結果が生ずることがあり 式の文字列の配列て、す ( 例 : TERM = execsp は , 次のようになるて、しよう。 VTIO の。環境には通常 , ユーザのホームデ ます。 int execs(cha 「 *cmdlin) ; / * 原型 * / List 2 て、は , もとのプロセスが fputs をコ ィレクトリ , 端末のタイプなどの情報が入 ールして , 1 行のテキストをファイルに書き っています。 argv と envp はともに , NULL int execse(char *cmdlin, char *envp [ ] ) ; 込んて、います。 fo 「 k をコールした後て、 , 子と ポインタて、終わっていなければなりません。 / * 環境版 * / 親の両方がファイルをクローズして , 両方 exec システムコールのそのほかの 5 つのタ のプロセスのバッフアの中身をフラッシュ イプは , 基本的には execve と同じて、すが , int execsp(char *cmdlin) ; / * パス名版 * / しています。その結果 , データはファイル 引数の形式がやや違います。 List 3 が , その execs 関数は文字列 cmdlin をトークン化し 完全な一覧表て、す。これらの関数は , 引数 に二度書き込まれます。 て引数の配列を作り , それを使って execv 関 を a 「 gv て、渡すか , argO, argl , argn, プセスの変容 数のどれかをコールします。トークン化は , NULL というリストて、渡すかという 2 グルー cmdtok 関数をコールして行います ( ソース プにわかれます。関数名はそれを反映して をお見せしていません ) 。 cmdtok 関数は , 一般的にプロセスは , 新たなプログラム いて , v がっくのが配列 (vector) 渡しの関 ANSIC の st 「 tok 関数と似ていますが , トー 数 , ーがっくのがリスト渡しの関数て、す。 を実行するために生成されます。システム クン間の連続するホワイトスペースを 1 個の コール exec は , 新たなプログラムを起動し p て、終わる関数は , ファイル名を引数に取 および て , それを exec へのコールを実行したプロ スペースとして扱い , \ , ります。ほかはパス名を必要とします。 p て、 、、 # 〃というメタキャラクタを認識します。 グラムと置換します。その新たなプログラ 終わる関数は , コール側プロセスの環境内 これらのメタキャラクタの機能は , シェル に定義されているパス ( たとえはア ATH = : / ムは , 最初から実行されます。コールした の場合と同じて、す [ 参考文献 1 ] 。 bin:/usr/bin のようなパス ) を探索して , プログラムが新たなプログラムて、置換され execs 関数を使う代わりに , 既存の exec 関 るのて , exec のコールは成功するとリター ファイルを見つけます。 ンしません。通常は ( ただし必ずて、はなく ) , e て、終わる関数は , 新たな環境を渡せま 数のどれかて、シェルを起動して , それにト ークン化をさせることもてきます。実行さ fork コールの直後に exec がコールされま す。ほかは , 現在の環境を新たなプログラ れるコマンド行の入っている文字列が , シ ムへコヒ。ーします。 す。 exec 関数は , 6 つのシステムコールをまと 6 つの exec 関数はすべて , 個々のトークン ェルへの引数として渡されます。 List int execl (path, argO, argl, 1 : int execle(path, argO, argl, 2 : int execlp(file, 3 : argO, argl, 4 : int execv(path, argv) 5 : int execve(path, argv, envp) 6 : int execvp(file, argv) char *path, *file, *argn, *argv ロ , *envp[] : NU しし ) argn, NUL し envp) argn, NULL) argn, UN Ⅸの spawn よ , われを助けたまえ / 39
ング添削 プログラ 変更は簡単て、すね。これも関数 keyscan( ) て、 ( 末尾の次 ) 」がどこて、あるかというポインタ 取得すれば解決します (List 2 の 51 行 ) 。 ( 先頭ポインタ , 末尾ポインタと呼びましょ ・キーバッフアのクリア う。配列ならば添え字に相当します ) , およ 関数 keyscan( ) は , 押されているキーをリ び「キューに入っているデータの個数」をも アルタイムに取得します。キーを押し続け たなければいけませんね。 てると , キーバッフアに押されたキーがた キー入力が行われたときは , 末尾ポイン まり , ヒ。ッヒ。ッビッと音がすることになり タの指す箇所にそのキー情報を格納します。 ます。てすからバッフアをクリアしないと バッフアからキー入力に関する情報を取り いけませんね。それが次の箇所て、す ( List 1 出すときは , 先頭ポインタが指す箇所から の 105 行 ) 。 行うことになります ( いずれの場合も処理後 はポインタをひとつずらします ) 。 while (kbhit( ) ) getch() ; 9 , こには問題が 2 点あります。第 1 点は , 実際のメモリ上には , Fig. 2 のように格納 関数 keyin( ) が呼び出されたときに矢印キー されます。具体的には 524H , 525H には先頭 Fig. 2 キー バッフアとその管理情報 などが押されていたら , 関数から return する ポインタ ( キューの先頭のオフセットアドレ のて、 , この文が実行されるという保証がな ス ) , 526H , 527H には末尾ポインタ ( キュー セグメント : 0 H いことて、す。キーバッフアのクリアを確実 の末尾の次のオフセットアドレス ) , 528H に はバッフアに入っている文字数が格納され に行うためには , 別の箇所 ( 関数から return ます。 する前 ) て、行う必要があります。 第 2 点は , getch( ) の使用て、す。残念なが 先頭ポインタ , 末尾ポインタに 502H を , らこれを使用しているかぎり , どんなにプ バッフア文字数に 0 をむりやり代入すれば , ログラムを改良しても , 高速処理は望めま キーバッフアをクリアしたことになります。 こて、は c ar kbuf というマクロ (List 2 せん。 PC ー 9801 のキーバッフアは , キュー形式て、 の 33 行 ) て、実現しました。関数 keyin( ) の先頭 記憶されます。キューはいわゆる待ち行列 (List 2 の 50 行 ) て、呼び出します。 51 E H 520H てすね。病院に行くと必ず ( ? ) 待たされま 次号では 51EH 先頭ポインタ 524H すが , 先に受け付けをすませた患者から診 510H 末尾ポインタ 526H 察します ( 特別な事情があれば別て、すが ) 。 キーバッフアのクリアに , Turbo C のラ 528H 9 文字数 イプラリて、ある poke, pokeb を使いまし 最初に入れたものから取り出すことから , え , 「 CMAGA セミナールーム」係まて、フロ た。これは任意のアドレスのメモリに値を FIFO(First ln First Out) とも呼ばれます。 ッヒ。ーディスクにてご応募ください なお , 入れる機能をもちます。処理系によっては ちなみに , キューと対になる概念がスタ ご応募いただいたフロッビーディスクは返 仕様が違っていたり (Zortech), サポートさ ックすなわち LIFO(Last ln Fisrt Out) て、す。 却て、きませんことをあらかじめご了承くだ れていないもの ( MS , Quick など ) がありま これは最後に入れたものを先に取り出すも す。 のてす。もし病院の待ち行列がスタックだ ( 1 ) 氏名・住所・電話番号 次号て、はそのような処理系て、も動作する ったらとんてもないことになりますね。後 ( 2 ) 使用機種名・コンパイラ名 ように poke, pokeb などの作成を行いま から来た患者を先に診察することになって , ( 3 ) 質問・相談事項 ( なるべく具体的に ) す。 早く来た人はいつまて、も待たされることに ・「添削プログラム」募集規定 なります。 宛先 「自作プログラムが期待どおりに動かない』 話を戻しましよう。 PC ー 9801 のキーバッフ 〒 108 東京都港区高輪 2 ー 19 ー 13 「もっと別なプログラミング方法はないもの は 16 個分の大きさをもっており , セグメン NS 高輪ビル か ? 』などのお悩みを解決します。数値計 ト 0 , オフセット 502H ~ 521H に格納されて ソフトバンク出版事業部 算 , グラフ理論や組み合わせ問題などのア います。キューは , 実際には配列のような C マガジン編集部 ルゴリズムに関する問題 , 事務処理 , デー リニアなデータ構造て実現されるのてすが , 「 C マガセミナールーム」係 タベース , システムプログラミングなど分 概念的には Fig. 1 のように環状てあると考え 野は問いません。右記必要事項を明記のう ます。そして「先頭」および「空き領域の先頭 Fig. 1 キー バッフアの概念図 先頭ポイン 502 51 E 506 5 OA 51 A 50E 末尾ハイン 51 6 512 O LLJ LL ()D X っム 0 5 キーバッファ 50EH 510 H プログラミング添削 147
Behind Me, LiSt #include く stdio. h> 48 : } Get Thee く errno. h 〉 く string. h> syscalls. h" 5POW0 0f UNIX! クローズされるようにて、きます。 し , exec がコールされるときに , 自動的に スクリプタの close - on - exec フラグをセット ステムコール fcntl を使って , ファイルディ される前にクローズしておくべきて、す。シ 要なファイルは , 新たなプログラムが実行 を設定するためには便利て、す。しかし不必 ムの標準入力 , 標準出力および標準ェラー はオープンのままて、す。これは , プログラ 1 : 3 : 5 : 6 : 7 : 8 : 10 : 12 : 13 : 14 : 15 : 17 : 20 : 22 : 23 : 24 : 25 : 26 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 37 : 38 : 39 : 40 : 42 : 43 : 44 : 45 : 46 : 47 : #include 2 : #include く stdlib. h> 4 : # i nc 1 ude #include char *cmdtok(char (s) : 9 : / * execs : ファイル ( 文字列パラメータ ) を実行 * / int execs(const char *cmdl in) char *tcmdl in = NULL; int argc ニ NULL; Char **argv char *p ニ NULL; / * コマンド行の作業用コヒ。ーを作る * / / * コマンド行用 * / / * 引数カウント * / / * 引数配列 * / / * 作業用の char ポインタ * / (char *)calloc ((size-t) (strlen (cmdlin) + 1), sizeof(*tcmdl (n) ) : tcmdlin strcpy(tcmdlin, cmdl (n) ; / * コマンド行から argv の配列を構築する * / argc ニ NULL; argv = cmdtok(tcmdlin) : P while ( 1 ) { / * 新たな argv 成分用にメモリをアロケートする * / (char * * ) real loc(argv, (size-t) ((argc + argv = NU しい { argvCargc] = NUL し : free(argv[argc]) : while (--argc > ニ 0 ) { / * メモリを開放 * / execv(argv[0], argv) : / * プログラムを実行する * / P = cmdt0k()U しし ) : strcpy(argv[argc 十十 ] , p) ; sizeof (*argv[argc])) : argv[argc] break; I) * sizeof(*argv))); (char *)calloc((size-t)(strlen(p) + 1 ) , free(argv) : free(tcmdlin) : return ー 1 : execl("-"/bin/sh' cmdlin, NULL); ルが fo 「 k をコールして , 新たなプロセスを作 て , 引数の配列を構築します。新たなシェ sh は cmdlin の中のメタキャラクタを展開し がコマンド行を実行する方法と同じて、す。 す。これは ,ANSI C のライプラリ関数 system を実行して終了するよう , sh に指示しま - c は , 次の引数 ( cmd ⅱ n ) の中のコマンド行 この方法の利点は , コマンド行の処理中に シェルの機能を使えることて、す ( もちろん , 必要に応じて execs のメタキャラクタの種類 を増やすこともて、きます ) 。ひとつの欠点 は , 余計なプロセスが生成されることて、す。 それに , もとのプロセスは新たなシェルの PID を知っていますが , そのコマンドを実際 に実行しているプロセスの PID は知らないの て、す。 exec 関数がコールされるとき , ファイル C のプログラムは , そのエントリポイント て、ある関数 main の第 2 引数を介して , 自分の 引数リストにアクセスて、きます。 intmain ( int argc, char * argvC ] ) て、 , argc は argv の 成分の個数にセットされます ( 最後の NULL を除き ) 。慣行的に , argv はプログラム 名 (execlp と execvp の file 引数 ) て、す。これは exec が要請していることて、はありません が , 実行されるプログラムがしばしば必要 とします ( a 「 gv [ O ] がプログラム名て、あるこ とを ) 。 環境へは , グローバルなポインタ extern char * * envi 「 on を使ってアクセスて、きま す。 UNIXC の実現系の多くが , exec に渡 す環境として , main が第 3 の引数 char * envp [ ] をもてるようにしています。ただ し , これは ANSI C 規格からの逸脱なのて、 , 環境へは envi 「 on を使ってアクセスしたほう が , プログラムの可搬性は大きくなります。 それに , en ⅵ「 on は実際の環境へのポインタ て、すが , envp はプログラムの実行開始時 の , 環境のコヒ。ーへのポインタて、あるにす ぎません。ライプラリ関数 putenv を使って 環境を変更すると , 環境の所在が変わり , envp のアドレスが無効になることもありえ ます。 セスの終了 プロセスはシステムコール , voidexit(int status ) を使って終了させます。 status は終了 するプロセスの親に報告される値てす。 status の値に ( 親と子が一貫した解釈をするかぎり り , 40 その引数配列て、 exec をコールします。 CMAGAZINE 19 10