参照 - みる会図書館


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

1. 月刊 C MAGAZINE 1991年7月号

を使って求め , 代入する。 Fig. 10 に示す方法がポインタの基本とな り , 配列や文字列などの処理をポインタを 使って行う。 こて , C 言語においてポインタを使用 し , 関数内て宣言している変数 ( 自動変数 ) をほかの関数て参照する場合の例を Fig. 11 に示す。 C 言語ては呼び出す側の変数ポイン C 言語ては呼び出す側の変数ポインタを関 数の引数とし , 呼び出された関数は , 呼び 出した側の変数をポインタにより間接的に 参照するのてある。 まず , 落ち穂拾いをしなくてはいけませ ん。前回の話の中て , インライン関数のご 利益をさもありがたげに述べたてましたが , SWAP ( ) マクロに対応するインライン関数 を提示しませんてした。 というのも , 関数 版 swap() を書くためには「参照型」の導入が 欠かせなかったからてす。 参照は , C 十十の初心者がまず飛び越えな くてはいけないハードルのひとってす。 C 言 語におけるポインタのように , これを把握 していないと話にならないというわけては ありませんが , 参照を使いこなせるかどう かて大きく差がついてしまうのて , キッチ リ抑えてください 参照は小粒で int&a; という記述は , int 型の変数を参 照するための変数として a を宣言していま す。「参照する」とはどういうことてしよう この連載の読者ともなれば , 「ポインタ」 などという概念は自明のものとして理解さ れていることと思いますが , それて、も参照 は難関てす。 120 C MAGAZINE 1991 7 このような手続きにおいて , C 十十ては , 参照型と呼ばれる機能を使用する。参照型 は , 変数に対し別名を指定する。 実践編 Fig. 13 参照型を用いた Fig. 1 1 の書き換え例 関数の型関数名 1 ( 引数リスト ) 変数 1 の宣言 関数 2 ( 変数 1 ) 関数の型関数名 2 ( テータ型 & 変数 2 ) 変数 2 ″関数 1 の変数 1 を参照 「参照する」とは , a という変数を介して何 かほかの変数にアクセスすることて、す。即 物的にいってしまうと , ほかの変数に a とい う名前を新たに割り当てたのと同じことに なります。 りません。したがって , いったんこのよう て , 参照型変数としての a のアドレスてはあ a て、求められるのは変数 b のアドレスてあっ の代入は b に代入しているのと同じこと。 & しているのとなんら変わりありません。 a へ a に対するあらゆる操作は , 直接 b を操作 られたのだと考えてかまいません。 b という変数に a という別の名前が割り当て ても , あくまても b の影のようなものてす。 繰り返しますが , a を「変数」と呼んて、はい ます。 参照するための変数として a が宣言されてい これなら正しい宣言て、す。 b という実体を int& a int b ; とり歩きしてしまっては困ります。 なのてあるから , ヾ a クという名前だけがひ 実体がなく , ほかの実体を指すための名前 は許されません。 a という変数そのものには ことになるのて , 上のような宣言は実際に つまり a はほかの変数の影として存在する 参照型は , 以下の形式て宣言する。宣言 時には , 必ずどの変数に対し別名を指定す るかを初期値として明記する。 データ型 & 名前 = 式 ( または変数名 ) : Fig. 12 に示すとおり , 変数 1 が対して別名 として変数 2 を定義し , 変数 2 によって変数 1 が参照てきることになる。したがって , Fig. 11 に示した手続きを , 参照型を用い , Fig. 13 のように記述することがてきる。 次回は拡張 C としての解説の最後として , ポインタ ( 今回の続き ) , 構造体を中心に話 を進める。 白倉伸一郎・山本浩文 に初期化してしまった a を , b てはない別の 変数の参照として定義しなおす方法はあり ません。 以下のような使い方が考えられます。 struct A { struct ETC { struct PASSWD { char* name ; int id : } passwd : } etc : このようにネストの入り組んだ構造体の 奧のほうに集中的にアクセスしたいような とき , struct PASSWD& passref a. etC. passwd ; とてもすれば , passref. name や pas sref. id などとしてアクセスすることがてき ます。 define< も同様のことカイきますが , 参照 を使えばスコープが限定されるのて , あと くされがありません。 また , const をつけて , const int& a のよ うに宣言すると , a の値を変更する操作が禁 止されます。最初から , const int tabstop 8 ; のように宣言された変数は値が固定され ますが , 値を変更したあとに途中から固定

2. 月刊 C MAGAZINE 1991年7月号

実力養成講座 3 したいような場合には ,constint&tabs tabstop ; のようにして , この tabs を使え ば , 元の tabst 叩の値を保護することがてき ます。 しかし , たかだかこれだけのメリットの ために , 新しい文法事項が導入されたわけ てはありません。参照の本分が発揮される のは , むしろ関数の引数として使われた場 合てす。 参照引数 関数引数として参照が渡された場合 , 実 際に ( コンパイラの吐き出すコードとしては ) 渡されるのは実引数のアドレスてす。これ によって , 関数は仮引数を介して実引数に アクセスてきます。つまり , 従来のポイン タ渡しにライバルが現れたということにな るのて、すが・・・ 参照て引数を渡すというカラクリ自体は , PascaI プログラマにはすてに「 VAR 引数」と してお馴染みのものて、す。しかし , どうし て Pascal 言語にこんな機構が採用されてい るのかといえば , 言語仕様上ポインタ引数 を受け取る手続き / 関数を書くのは骨が折れ るのて , それを解決するための臨床的な措 置だと考えられます。それにひきかえ C 言語 は , 野放図なほど自由自在にポインタを操 れるのを売り物にした言語てはありません か。そこに参照渡しという選択肢を加える ことにどんな意味があるというのてしよう こて話を swap( ) に戻します。前回の SWAP( ) マクロは , #define SWAP(), b) { int t いう定義てしたが , これを関数て書くと , inline void swap(int& a, int& b) inline VOid swap(int* a, ンタを使って , のようになるてしよう。しかし一方 , ポイ int* b) int t ために , たとえば TurboC はこの型を構造 アドレスとして渡される型として規定する また setjmp() に関していえば ,jmp buf を 下りることになります。 と思います。プログラマの肩の荷がひとつ いちじるしく軽減されることはおわかりだ ように書かれていれば , そういう危険度が しかし , この手の関数が参照引数をとる 仕方がありません。 てライプラリのインプリメントを責めても ハングアップしたからといっ ったときに NULL ) だのという呼び出し式を書いてしま えば setjmp(NULL) だの , intdos(NULL, 数を渡すのはプログラマの責任てす。たと 提にしています。これらの関数に正しい引 ポインタが必ず実体を指していることを前 このうち①を別とすれば , ②③の大半は ③実引数に直接アクセスするため ②値渡しのオーバヘッドを避けるため 現できないため ① cha 「 * や void * などポインタでなくては表 の三つに大別されます。 関数がポインタを引数にとる理由は , 次 さい無用てす。 参照引数を使う限り , そんな気遣いはいっ もしれませんが , 単なるムダて、す。しかし クするのは , 心がけとしては感心されるか が NULL て、あるかどうかをいちいちチェッ す。だからといって , swap ( ) の内部て引数 swap() は NULL を渡しては危険な関数て、 に相当するものがありえません。 はポインタていうところの「ヌルポインタ」 てはなりません。具体的にいうと , 参照に 参照は影てあり , 必ず実体の別名てなく 参照は必ず実体を指さなくてはならな 重要な違いは次の 1 点に尽きます。 は 1 バイトたりとも違いはないてしよう。 を使っても , おそらく生成されるコードに ます。そして , 参照を使っても , ポインタ などとしても , 機能的には同じものが書け スタートアップ C 十十 体 1 個の配列の typedef として定義するという ヾテクニック〃を使っています (jmp buf 問 題に関しては , 本誌 ' 90 年 12 月号の「 ANSIC ー more 」を参照 ) 。これも参照が導入される ことによってスマートに解決されてしまう 問題のひとってす。 「必ず実体を指さなくてはならない」とい ういい方をすると , あたかも制限のように 聞えますが , 実際には , 「必ず実体を指す」 という保証として働いてくれるのてす。 変数の初期化を保証することは C 十十の大 目的のひとつだと前回から述べていますが , 初期化抜きには宣言することすらてきない 参照はまさにその代表選手てす。仮引数も 変数の一種てあると考えられますが , 関数 呼び出しはこの「引数という変数」の初期化 てあるということがてきます。 おもしろいのは , int& a b : のよう に , 参照は何か名前のある実体を指ずのが 普通てすが , 名前のない実体を指す場合も あることてす。参照の宣言が無理やり実体 を作ってしまう場合があるのてす。たとえ ば , int& a 2 ; という宣言はエラーには なりません。 2 という値をもつ int 型の実体が 暗黙のうちに生成されて , その見えない実 体への参照として a が宣言されるのてす。 ういう宣言が許されるのは , この例の 2 → int のようにキャストなして暗黙の型変換が働 くような場合に限られます。 つまり , swap(), 3 ) などという呼び出し も文法上は可能てす。もちろん , 暗黙のう ちに生成されたこの実体には変数としての 名前がなく , アクセスてきないのて実用価 値はないかのように見えます。しかし , た とえば Turbo C や MS-C の lfind( ) 関数の第 3 引数のように , 引数がポインタてあるがゆ えに , いったん一時変数に即値を代入して からそのアドレスを渡さなくてはいけない といった二度手間が要求されるようなこと 参照を関数の戻り値として返すこともて 参照返し はなくなるてしよう。 スタートアップ C 十十 121

3. 月刊 C MAGAZINE 1991年7月号

スタートアップ C 十十 実力養成講座 3 Fig. 8 誤ったマクロ関数 Fig. 7 プリプロセッサによる置換 #define max(), y) (x>y?x:y) main( ) main() = max(IO/2, 5 * 2 ) : int X = max()0 , 5 ) : int X int X = ( 10 / 2 > 5 * 2 ? 10 / 2 : 5 * 2 ) : int X Fig. 9 インライン関数の例 int ⅵ inline int max(int x, Fig. 10 ポインタの基本 retu 「 n (x > ⅵ : x:y , int i : 5 int * p , int x : * p , 〃ポインタ変数の宣言 〃ポインタ変数へのアドレスの代入 〃ポインタ変数による参照 main( ) ニ max(IO/2, 5 * 2 ) : int ー第 int m : (int x : 10 / 2 > int y = 5 * 2 ) ? x:y : 相当する機能がある。マクロとの違いは引 だし , static な関数 ) として扱われる。 する変数と同じ型てポインタ変数を宣言す ・関数内にループ制御文があってはならな 数の置換が通常の関数と同様に行われ , 引 る。ポインタ変数の宣言ては , ポインタ変 数や返り値のデータ型のチェックが行われ 数の前に * クをつける。そして , ポインタ ・関数の定義の有効範囲は定義された箇所 る点てある。つまり , インライン関数は通 変数に参照する変数のアドレスを & ク演算子 からファイルの終わりまで 常の関数と同様に使用て、きる。マクロて、は , ・関数を使用する前に定義されていなけれ 先の Fig. 7,Fig. 8 のように置換されること Fig. 11 C 言語による自動変数の参照例 関数の型関数名 1 ( 引数リズト ) ばならない を前提に定義しなければならないが , イン ライン関数ては , 通常の関数のように定義 変数 1 の宣言 関数 2 ( & 変数 1 ) ボンタ てきる。 Fig. 9 は , 先に示したマクロをインライン 関数の型関数名 2 ( テータ型 * 変数 2 ) C 十十てのポインタに関する処理は , C 言 関数とした例と , 実際とは異なるがインラ * 変数 2 〃関数 1 の変数 1 を参照 語と同様てある。ポインタを使用する場合 イン関数が展開された例て、ある 0Fig. 9 から もわかるように , インライン関数は , 一般 は , まずポインタ変数を宣言し , そのポイ の関数と同様のセマンティックスて展開さ ンタ変数に , 変数のアドレスを代入する。 そして , ポインタ変数の指す領域を参照す Fig. 12 C 十 + の参照型 れる。 インライン関数の定義は , Fig. 9 に示すよ テータ型変数 1 : うに , キーワード inline を記述して定義す Fig. 10 ては , int 型の変数 i をポインタを使 テータ型 & 変数 2 = 変数 1 : って間接的に参照している例てある。この る。ただし , 次のような制限があり , この 変数 2 : 値 : / / 変数 1 を参照している 制限が満足されない場合は , 通常の関数 ( た ように , ポインタを使った処理ては , 参照 119 スタートアップ C 十十

4. 月刊 C MAGAZINE 1991年7月号

スタートアップ C 十十 実力養成講座 3 ラムの起動時に変数が定義される。また , も定義の前て ( Fig. 3 ー右の関数て、変数 2 ) を使 内 ( プロック内 ) て、記述した場合は , そのプ 変数の定義はプログラムが複数のファイル 用する場合には , その変数を使用する前に ロック内が extern 宣言の有効範囲となる に分割されている場合て、も , ひとつのファ 次の宣言を行わなければならない (Fig. 4 ) 。 イルて定義する。 extern データ型変数名 外部変数は , 初期化を行わないと定義時 外部変数はひとつのファイルて , なおか この extern 宣言は , 変数を定義するもの に「 0 」に初期化される。「 0 」以外の値て初期 つ関数の外側て定義することて , すべての てはない。さらに , extern 宣言は関数の外 化を行う場合は , 以下の形式によって初期 関数て使用 ( 参照 ) て、きる変数となる (Fig. 側と関数内 ( プロック内 ) て、記述て、きる。関 値を与える。また , 外部変数て、は , 配列の 3 ) 。外部変数が定義されていないファイル 数の外側て、記述した場合は , extern 宣言の 初期化を行うことがてきる。 (Fig. 3 て、は , 変数 1 を Fig. 3 ー右の関数 ) て、使 有効範囲は , 記述したところからファイル データ型変数名 = 定数 , 用する場合や , 定義されているファイルて、 の終わりまてのすべての関数となる。関数 データ型配列名 [ 要素数 ] = { 定数 , ... } ; なお , 配列の extern 宣言て、は , 1 次元配列 Fig. 3 外部変数の定義 において要素数の省略が許される。 静的変数 テータ型変数 1 : / / 外部変数の定義 関数の型関数名 ( 引数リスト ) 静的変数は , 変数の定義の際にデータ型 の前にキーワ ード static を置く (Fig. 5 ) 。 static デ ータ型変数名 , 静的変数は , 関数の外側と関数の内側に 記述てきる。関数の外側に記述した場合は , そのファイル内の関数て、有効 ( 参照可能 ) と なり , 別ファイルの関数からは見えなくな る ( 参照てきない ) 。また , 関数内の static て、 は , 関数内て、有効てあるが , 自動変数との 違いは , static な変数はプログラムの起動時 に定義される点てある。つまり , 自動変数 は , 処理がプロックに達したときに変数が 定義され , プロックの処理が終了すると変 数の領域は解放される。しかし , 関数内の 静的変数はプログラムの起動時に変数の領 域が割り当てられ , プログラムの終了まて、 解放されない 関数内の static 変数は , プログラムの終了 まてその領域が確保されているのて , 関数 が呼び出されて処理した結果を , 次にその 関数が呼び出されたときに利用てきる。 静的変数も , 外部変数と同様に , 初期値 を指定しない場合は「 0 」に初期化され , 「 0 」 以外の値て初期化する方法も外部変数と同 様て、ある。注意しなければならないのは , 関数内の静的変数も , 関数の外側て定義さ れた変数と同様にプログラムの起動時に一度 だけ初期化される点てある ( 関数が呼び出さ れるたびに初期化されない ) 。 関数の型関数名 ( 引数リスト ) テータ型変数 2 : / / 外部変数の定義 4 exte rn 宣言 Fig. extern データ型変数 2 : テータ型変数 1 : 関数の型関数名 1 ( 引数リスト ) 変数 1 , 変数 2 の参照ができる 関数の型関数名 2 ( 引数リスト ) 変数 1 , 変数 2 の参照ができる 関数の型関数名 1 ( 引数リスト ) extern テータ型変数 1 : 変数 1 の参照ができる テータ型変数 2 , 関数の型関数名 4 ( 引数リスト ) 変数 2 の参照ができる Fig. 5 静的変数 ファイル 1 static テータ型変数 1 ・ 関数の型関数名 1 ( 引数リスト ) static テータ型変数 2 : 変数 1 , 変数 2 の参照ができる static データ型変数 3 , 関数の型関数名 2 ( 引数リスト ) 変数 1 , 変数 3 の参照ができる 別変数として扱われる ファイル 2 static テータ型変数 1 : 関数の型関数名 3 ( 引数リスト ) 変数 1 の参照ができる スタートアップ C 十十 117

5. 月刊 C MAGAZINE 1991年7月号

struct A a ; return &a のような場合てす。もちろん , pointer() 関 数からリターンしたとたんにスタックフレ ームが解放されてしまい , 不定な実体を指 すポインタを返すことになってしまいます。 参照を返す関数の場合も同じことてす。 16 : { 18 : } 24 : ) 33 : ( state. stat = InCharConst; きます。 ポインタて戻り値を返す場合 , 気をつけ なくてはいけないのは , 自動変数のアドレ スを返してしまうミスてす。たとえば , struct A { struct A * pointer( ) 76 : } 79 : { 83 : } return a ; struct A a ; しまうことになります。しかし , 不定な〃 これもやはり , 不定な実体の参照を返して うに初期化すればよいのてしようか ? のてしようか ? そして , いったいどのよ てす。これはいったい何を指すことになる うてす。 void (&function)( ) : のような宣言 文法的には , 関数への参照もありうるよ 関数への参照 ってくれるてしよう。 てあれば , 必ずコンパイルエラーとして扱 のて , よくてきた処理系 ( TurboC 十十など ) 実体への参照などというものはありえない struct A& reference( ) 関数への参照に関して厳密に規定してい プログラムの内容に入る前に , main( ) の 力するプログラムてす ( List 1 ) 。 は標準入力のタブをスペースに展開して出 -n(n = タブ数 ) のようにして , ファイルまた ルプログラムをあげてみましよう。 detab 今回まてに登場した機能を使ったサンプ d eta b 必要はありません。 の曖昧な仕様にいちいち目クジラをたてる ようてす。発展途上の言語なのて , この手 るのてはないか」という程度の意味しかない なら , 関数への参照というものも考えられ ん。現時点ては , 「関数へのポインタがある に取り扱うことのてきる処理系もありませ るドキュメントは見当たらないし , まとも LiSt 6 : 7 : 8 : 9 : 11 : 14 : 15 : 17 : 22 : 23 : 25 : 28 : 35 : 36 : 37 : 38 : 39 : 41 : 42 : 43 : 44 : 45 : 46 : 48 : 49 : 50 : 51 : 34 : 32 : void SwitchState(State& state, const char c) int ignore; Attrib stat; 27 : struct State ( 26 : enum Attrib ( 円 ain, lnString, InCharConst ) : putchar(' ' ) : for (int れ bl 囲 k = Rest(col, tabstop) : れ bl 聞 k 〉 0 : detab. cpp ( タブコンバートプログラム ) 1 : 〃 detab. cpp tab ー〉 s ce(s) converter 2 : 〃 $Log: RCS/detab.cpp 3 : 〃 revision 1.1 hirofumi 91 / 04 / 12 06 : 27 : 22 4 : 〃 lnitial revision 5 : 〃 include く stdio. h 〉 include く stdlib.h> include く string. h 〉 10 : #include く jctype. h> 12 : #define FALSE 0 13 : #define TRUE 1 inline int Rest(int C01 , int ね top ) return tabstop ー ( C01 % tabstop); 20 : void PutTab(int& C01 , const int tabstop) 十十 001 , —nblank) 52 : 53 : 55 : 62 : 63 : 64 : 65 : 69 : 75 : 77 : 80 : 82 : 66 : 68 : 56 : void DeTab(FILE* fp, int ね t 叩 , int isC) if ()p = の return : setvbuf(fp, NULL, —IOFBF, BUFSIZ * 8 ) : State state = PIain, FAISE } ; for (int c. co = 0 ・ (c = getc(fp)) ! = EOF; ) break; if (c = if ( ! isC state. stat = Plain) PutTab 81 , ね top ) : else putchar(c), C01 ← Rest(col, ね t 叩 ) : SwitchState(state, c); + + C01 : else 81 三 0 ; if (c = ) putchar(c); ) else ( 78 : li れ e int GetTab(const char* s = 0 , int min = 1 , int def ま 8 ) if (s = 0 atoi(s) く min) return atoi(s) ; 122 if (state. ignore) state. ignore = FALSE; else if (iskanji(c) Ⅱ c ー state. ignore = TRUE ・ else switch (state. Stat5 case Plain: if (c = ' state. stat = InString; else if (c = break; case lnString : if (c = state. stat = Plain; break; case InCharConst: if (c = state. stat = 円 ain; 1991 7 84 : 88 : 89 : 90 : 92 : 93 : 94 : 95 : 96 : 98 : 99 : 100 : 101 : 102 : 85 : int argc, char** argv) int ね t 叩 = GetTab(); int count = 0 : int isC = 駅囲 : while (—argc) if ( + + ar ま = ' ーっ isC = ! isC; else tabstop = GetTab(*argv); else return 0 ; DeTab(stdin, tabstop, isC) ; if (count = の DeTab(fopen(*argv, ” rt ” ), tabstop, isC), + + co t : C M AGAZI N E

6. 月刊 C MAGAZINE 1991年7月号

記憶クラスが指定されていないか , ある ムになるとそれらは同じアドレスに配置さ ていきます。 いは static の場合 ( つまり extern< ないとき ) れます。ゆるい Ref/Def モデルの例を List 10 コモンモデルは Dennis Ritchie のオリジナ には , その宣言は「仮の定義」てす。定義と ルの C が採用しているモデルて , FORTRAN に示します。 いうからにはオプジェクトに対してメモリ 厳密な (Strict)Ref/Def モデルては , ゆる の Common に基づいています。これは , extern が割り当てられます。しかし , 「仮の」とい が指定されているかどうかに関係なく , 宣 い Ref / Def の制限に加えて , プログラムを構 言イコール定義と考えてメモリを割り当て う形容詞が示すように , その場てメモリを 成するすべてのファイル中に名前に対する 割り当てるのてはありません。その後にイ ます (List 9 参照 ) 。ふたつのファイルて i は 定義はただひとってなければなりません。 ニシャライザを使った定義があれば , 初期 extern つきの宣言しかなく , j は記憶クラス 同じ名前に対する複数の定義があるときに 値とともにメモリを割り当てます。そのよ なしの宣言しかありません。 はリンク時にエラーになるてしよう。 List 11 しかし , これらはいずれも定義として処 が厳密な Ref / Def モデルの例てす。 うな定義が存在しないときには , あたかも 理されるのて , これらの間の違いはまった ファイルの最後て初期値が 0 てイニシャライ 初期化 (lnitialization) モデルては , 名前を ズされたかのように扱われます。 くありません。コモンモデルては , external 定義するためには初期値が必要てす。初期 この「仮の定義」が現れたときに , すてに リンケージて、同じ名前を持つオプジェクト 化されたものだけが定義されたことになり 同じ名前に対する宣言 , または仮の定義が や関数は , 同じアドレスに重なるようにリ ます。 List 12 は , i と j を初期化することによ って定義しています。このモデルては extern 存在していてもかまいません。つまり , ンクされます。 指定の存在にかかわらず , 初期化されてい UNIX の C コンパイラはゆるい (Relaxed) Ref/Def モデルを採用しています。このモデ ない名前は単に宣言されたものにすぎませ ん。オプジェクトを定義するためには必ず というふうに同じ名前に対する仮の定義を ルては , extern が指定されると定義て、はな 何度も書いてもよいということて、すにれに 初期化しなければならないという点て , 最 く , いわゆる宣言として扱います。したが も筆者は驚きました。二重定義のエラーだ も制限がきついモデルてす。 って , メモリは割り当てられません。しか と思いませんか ? ) 。このように , 複数の宣 し , 定義に関してはコモンモデルと似てい ANSI 規格ては , 厳密な Ref/Def モデルと 言や仮の定義があるときには , 名前の型と 初期化モデルの組み合わせを採用していま ます。 す。厳密な Ref / Def モデルからは「定義は全 リンケージがすべて一致していなければな いくつものファイルに同じ名前の定義が あったとしても , リンクして 1 本のプログラ ファイル中にただーっだけ存在しなくては りません。一致していないときにはエラー になります。同じ名前に対する宣言がいく Table 4 Ref/Def モテル つもあるときに , 記憶クラス指定子の組み Ref/Def モテル名 合わせて、どのように解釈されるかを示した のが List 8-a ~ c てす。 List 8-c の extern を 指定している j の宣言を見てください。これ はリンケージ決定の規則③ー 1 から internal リ ンケージになることに注意してください ー Ref/Def モテル ファイル間ての名前の参照と定義につい て説明します。複数のファイルの間ての名 前の特定の方法についての話なのて , オプ ジェクトや関数を表す名前は external リンケ ージのはずて、す。 internal リンケージの名前 はファイル内て定義と参照は解決されてい なくてはなりませんし , このような名前は 他のファイルからは見えません。 名前の参照 (Reference) と定義 (Definition) の扱い方を四つにモデリングします ( Table 4 ) 。これらは上から順に制限がきつくなっ 96 C MAGAZINE 1991 7 意 FORTRAN のコモンと同じく , 記憶クラス指定子 extern が指定さ れているかどうかにかかわらず , すべての extern 引リンケージの宣 言でメモリを割り当てます。各ファイルがリンクされるときには同 じ名前はひとつにまとめられて同じアドレスに配置されます。これ はもともと Dennis Ritchie が設計した C で採用していました コモン ( Common ) 記憶クラス指定子 extern が指定されている場合には , それを名前の 定義ではなく宣言とみなし , 名前にはメモリを割り当てません。 extern を指定しない宣言がその名前の定義になります。プログラム を構成するファイルのどこかに名前の定義が少なくともひとつは存 在しなければなりません。ひとつ以上であれば , いくつ定義されて いてもかまいません。また , extern 付きの宣言はあるが , その名前 がどこにも使われていない場合は , その名前の参照を生成する必要 はありません。つまり , 名前の宣言が存在しなかったと考えてかま いません。 UN Ⅸの cc はこのモデルをインプリメントしています ゆるい (Relaxed) Ref/Def 厳密な (Strict) Ref/Def これは上のゆるい Ref / Def とほぼ同じですが , 名前の定義は全ファ イル中でただひとつだけに限られます。これは K & R のモテルです 明示的に初期化されている名前だけが定義されます。それ以外は , extern の有無にかかららず , すべて名前の参照で実体は定義されま せん 初期化 (lnitialization)

7. 月刊 C MAGAZINE 1991年7月号

lnformation from Compiler Makers とプロトタイプて near または far と ラムに渡す引数がなくてもヌル文 明示的に記述してある関数てす。 字列を学えないといけません。 DOS 異なるメモリモデルてコンパイル の内部コマンドを実行する場合は , されたモジュールをリンクしよう COMMAND.COM をロードする とすると , リンカはエラーを生成 必要があります。それから内部コ します。リンカがほかのセグメン マンドの /C を , Exec('A:.COM トに存在するオプジェクトへの MAND.COM/, '/C DIR * . near 参照に出会うと , fixup のため PAS') : というように指示します。 に 4 バイトアドレスを 2 バイトに取 最後に , Exec の失敗にはメモリ っておくようにてきません。そこ 不足が考えられます。これに対処 <fixup overflow" が発生します。 するには , 最大ヒープサイズを可 TurboC 十十ては , 異なるメモリ 能なかぎり低い値にセットします。 モデルてコンパイルされたオプジ これは $M 指令 , または IDE の ェクトモジュールを混在している Options/Memory Sizes メニュー 場合がほとんどてす。 こては , を使用してください たとえば , small モデルのオプジ $ M 指令の使用をお勧めします。プ ェクトを large モデルのファイルに ログラムをどのようにコンパイル リンクするときてす。解決方法と しても , 正しいメモリアロケーシ しては , リンクするすべてのオプ ョンを保証します。 ジェクトファイルを削除して同じ Q メモリモデルて再構築を行うこと Turbo C 十十では複数のモジ てす。 ュールをリンクするときに , リン near および far のキーワードを明 カカ 0fixup ove ow ″を出力しま 示的に使ってポインタのデフォル すがどうしたらよいですか ? トサイズをオーバライドするとこ A のエラーメッセージが発生します。 プログラムをコンパイルする しかし , ほとんどの場合は , 関数 ときに , コンパイラはすべての関 呼びしが原因て、す。どの場合て 数に対して参照を行います。この fixup overflow" は呼ばれて 参照は , 関数のアドレス ( リンカが いる関数と呼び出したモジュール 呼び出されるまて確定しない ) て埋 をリポートするのて , 問題の場所 められます ( または "fixed up" され を見つけることはそう難しくない ます ) 。 てしよう。 tiny, small, compact メモリモ デルを使用している場合 , これら ・お知らせ の参照はデフォルトては near ( 2 バ 引越などて住所変更のあった方 イト ) コールとなります。 med は , ユーザ ID 番号とともに新しい ium, large, huge< コンノヾイノレす 住所を郵送にてご連絡ください。 ると , 関数はほかのセグメントに 住所変更のご連絡いただけない場 存在する可能性があるのて , 生成 合は , アップデートそのほかのご された関数の参照はデフォルトて は far ( 4 バイト ) になります。 案内を郵送てきない場合がありま ただひとつの例外は , 関数定義 す。 TabIe 1 Textmode() 関数のバラメータ変更 Turbo C2.0 Tu rbo C 十十 1 .0 内容 VL8025 N8025 漢字表示可 BG8025 E8025 簡易グラフィック表示 intdos( ) を使用した曜日取得プログラム 1 : #include く stdio. h> 2 : #include く dos. h 〉 3 : 4 : char getyobi (void) 6 : union REGS regs : 7 : 8 : regs. h. ah = 0x2a; 9 : intdos (&regs, &regs) : return(regs. h. (l) : 12 : } 13 : 14 : void main(void) 15 : { Char yobi : 20 : 22 : 23 : 24 : 25 : 26 : 30 : 32 : 33 : 34 : 35 : 36 : 38 : 39 : 40 : 42 : 44 : 46 : } List 1 = getyob i ( ) : YOb i printf("lt is " ) : switch (yobi) { case 0 printf("sunday") : break : case 1 printf("monday") ; break : case 2 printf("tuesday") : break : case 3 printf("wednesday") : break : case 4 printf("thursday") : break : case 5 printf("friday ” ): break : case 6 printf("saturday") : break : printf(" today. Yn つ : lnformation from Compi 厄「 Makers 159

8. 月刊 C MAGAZINE 1991年7月号

00 Q プログラ ミング研究 特 集 Table 1 プロセスの常駐 Microsoft C Optimizing Compiler —dos—keep 関数 (MS-C) Ver. 6. OOA # include く dos. h 〉 Turbo C Ver. 2.0 void —dos—keep (retcode, memsize); Turbo C 十十 Ver. 1 .01 終了ステータスコード unsigned retcode, 常駐するメモリサイズ ( ノヾラグラフ数 ) unsigned memslze,• 三つの処理系を取り上げ , これらのコン パイラが持つインラインアセンプラや疑似 DOS ファンクション 31h を実行してプログラムを常駐させる 変数などの機能を一部利用して , C 言語だけ keep 関数 ()u 「 bo C/ C 十十 ) <TSR70 ログラムやデバイスドライバを記 # include く dos. h 〉 VOid keep (status, size); 述する方法を紹介します。 終了ステータスコード unsigned cha 「 status, 本特集て、は誌面の関係て、いろいろなケー 常駐するメモリサイズ ( ノヾラグラフ数 ) unsigned size; スごとの詳しい紹介がて、きないのて、 , より DOS ファンクション 31h を実行してプログラムを常駐させる 割り込みべクタ 0 , 4 , 5 , 6 を起動前の状態に戻すため注意すること 詳細に知りたい方は参考文献 [ 3 ] もあわせ て参照してください タ 4 , 5 , 6 が起動前の状態に戻されるため , 定周期で起動される まずは , 小手調べに簡単な TSR プログラ 注意してください。 PC ー 9801 てはキ ②キーポードハードウェア割り込み , RS- ムを C 言語て記述してみます 0MS-DOS のメ , 皿キーの割り込みがこれらに該当し 232C 割り込みなどカ生したときに起動 モリ管理については簡単に触れるだけにし ますから , TSR プログラムがキー ますから , 詳細については参考文献 [ 2 ] , [ 3 ] される キーに割り込む場合には , ひと工夫必 ③ソフトウェア割り込みにより呼び出され などを参照してください 要てす。 る などの方法て、呼び出されます。 プログラムの常駐 プログラムを常駐させるには DOS ファン クション 31h を実行します。ただし , C 言語 て、はスタートアップルーチンて、 0 除算割り込 通常のプログラムは実行終了後はメモリ Table 1 に示した dos keep 関数 , keep 関 みや浮動小数点用の割り込みなどがセット 領域が解放されますが , TSR プログラムは 数は , いずれも常駐するメモリサイズを指 アップされているため , これらの割り込み プログラムの実行終了後もメモリ内にとど 定しなければいけません。そのためにはメ べクタを元に戻す必要があります。そのた まり , MS ー DOS の標準の機能にない機能を め , 通常の処理系にはライプラリ関数が用 モリ構造を知る必要があります 0Fig. 1 に MS 提供します。 -C, TurboC / C 十十のスモールモデルの一 意されています ( Table 1 ) 。 常駐した TSR プログラムは , TurboC / C 十十の場合には割り込みべク 般的なメモリ構造を示します。 ①タイマ割り込み , VSYNC 割り込みなどで Fig. 1 スモールモテルの一般的なメモリ構造 下位アドレス① MS ー C のスモールモテル 十 0 十 1 十 2 十 3 十 4 十 5 十 6 十 7 十 8 十 9 十 A 十 B 十 C 十 D 十 E 十 F MCB PSP 領域 コード領域 ( 常駐部分 ) コード領域 ( 初期化処理 ) 初期化されているテータ領域 初期化されていないテータ領域 スタック領域 nea 「ヒープ領域 fa 「ヒープ領域 常駐サイズ し」 下位アドレス @Turbo C のスモールモテル 十 0 十 1 十 2 十 3 十 4 十 5 十 6 十 7 十 8 十 9 十 A 十 B 十 C 十 D 十 E 十 F MCB PSP 領域 CS コード領域 ( 常駐部分 ) コード領域 ( 初期化処理 ) SS, DS DS 初期化されているデータ領域 ((unsigned)&end 十 OxOf) 》 4 DGROUP 初期化されていないテータ領域 end nea 「ヒープ領域 スタック領域 fa 「ヒープ領域 ー—PSP —PSP CS SS, DS —PSP DS (——heapbase 十 OxOf) 》 4 c—heapbase DGROUP 上位アドレス 上位アドレス 特集 TSR プログラミング研究 39

9. 月刊 C MAGAZINE 1991年7月号

YVARRY*README は付録ディスク収録 ' 91 年 7 月号特別付録 ( 5 ″ 1.2M 8 セクタ / 権者 ( 小橋ゆたんぼ氏 , 藤井びろしき氏 ) の 版仮想配列ライプラリ使用条件のドキュメ、 トラック MS-DOS フォーマット ) には , 次の 了解を得て収録しました ントてす。 プログラムが収録されています。 103. EXE は自動解凍圧縮ファイルてす。詳 ①付録ディスクの説明 しくは README をご参照ください。また小 バッチ式メニ = ー rBMJ READM E 橋氏の「富国強兵について , いくっかのコメ ( テキストファイル ) ②仮想配列ライプラリ「 VARRY 」 ント」 (*WANDPYREADME) というメッセ 本誌 ' 91 年 6 月号「フリーソフトウェア最新 YVARRY くディレクトリ > ージも収録しました。ご一読ください レポート」て紹介した「 BM 」てす。著作権者 ③バッチ式メニュー「 BM 」 叭 NSI C 言語入門講座」 YBM ( 高崎信太郎氏 ) の了解を得て収録しました。 くディレクトリ > 活用集④ ④ X68000 に移植された GNU C Compiler ③ YBM*BM231. EXE は自動解凍圧縮ファイ YXGCC3 ルてす。詳しくは README をご参照くださ 今月の BOHYOH ディレクトリは , CON ・ < ディレクトリ > ⑤バーコード印刷プログラム い。また高崎氏からのメッセージ「ようこそ FIG. SYS や AUTOEXEC. BAT を起動時に YBARPRN BM の世界へ」 (YBMYBM. TXT) も収録し 選択てきるプログラム CNFMNU. SYSC くディレクトリ > ⑥富国強兵「 WANDP 」 す。 CNFMNU. DOC をご一読のうえご活用 一読ください YWANDP ください くディレクトリ > X68000 に移植された ⑦「 ANSI C 言語入門講座」活用集④ GCC バグフィックス版 X68k 版 LHA 修正バージョン YBOHYOH くディレクトリ > ⑧ X68k 版 LHA バグフィックス 本誌 ' 91 年 5 月号て収録した XGCC に文字 *LH AX 68 列リテラルバグが発見されました。本誌 本誌 ' 91 年 6 月号に収録した Human68k 用 くディレクトリ > 「 GCC て学ぶ 68 ゲームプログラム」の筆者 , 吉 ⑨本誌掲載プログラム LHA に HUMAN. SYS(Ver. 2.02 ) を LHA *CMAGA 野智興氏のご好意により , バグフィックス < ディレクトリ > にて圧縮すると解凍不能なファイルを生成 した gcc ccl. x と修正したソース ( extra ー 付録ディスクの説明 (README) に , 解凍 してしまうといラバグが発見されました。 10 叩 . c ) およびバグ情報 (README. DOC) を 方法など , さらに詳しい説明が収録してあ バグフィックス版を収録いたします。詳し 圧縮し ,GCC. LZH として収録しました。解凍 りますのて、必ずご一読ください 0README くは README をご参照ください。 方法については README をご参照くださ はテキストファイル形式てす。 MS-DOS の 本誌掲載プログラム い。なお , 6 月号より収録を開始した XGCC の TYPE コマンド , あるいはご使用のエディ ソースファイル収録は次号から再開します。 タ , ワープロソフトて、読むことがてきます 9 REXX : バッチ言語を超えた ~ 仮想配列ライプラリ「 RRY 」 バーコード印刷プログラム TOKUSYU : TSR70 ログラミング研究 ALGO : アルゴリズムとデータ構造 本誌「 Conference Room 高速仮想配列 本誌 ' 91 年 1 月号に収録し , ご好評いただい MSDOS : 新 MS-DOS 入門 ライプラリⅣ ARRY 」」をご覧いただけたて、 たバーコード印刷プログラムの SPARC LT OUYOU : 応用 C 言語 C の道具箱 しようか ? 補助記憶とバッフアを利用し 版てす。詳しくは本誌掲載記事「パソコンか CZATU : C 言語雑学講座 て大きな配列を使え , 高速化を図った仮想 らワークステーションへ」をご一読くださ DOJI : 恥かしながらドジりました 配列ライプラリてす。収録内容は , 。なお , 自動解凍圧縮ファイルとして収 MEIKAI : 明解 ANSI C 言語入門講座 YVARRYYSRC ディレクトリに仮想配列ラ 録しています。解凍方法については READ CPPJ : スタートアップ C 十十 イプラリのソースとサンプルプログラム X68K ME をご参照ください。 : GCC て学ぶ 68 ゲーム SAMPLE ℃を , *VARRY*TEST ディレク OPPK : ワンポイントプログラミング 富国強兵「 WAND 円 トリに本誌掲載記事て参照しているテスト TENSAKU : C MAGA セミナールーム プログラムを ,YVARRYYEXE ディレクト PUZZLE : C マガ電脳クラブ 本誌 ' 91 年 6 月号「フリーソフトウェア最新 リにサンプルプログラムとテストプログラ NIWA : 丹羽信夫の発想快発 レポート」て紹介した「 WAMDP 」てす。著作 ムの実行ファイルを収録しています。 INFO : インフォメーション ディスク内容のお知らせ 167

10. 月刊 C MAGAZINE 1991年7月号

に一彡売 Fig. 2 LRU アルゴリズムによる仮想配列の管理 参照の局所性と呼ばれる性質があります。 これはある期間のプログラムのふるまいを 見るとそうなる確率が高いということて、 , 時間的に常に成り立つものてはありません が , 一般的に多くのプログラムがこの性質 を満たします。 そこて主記憶上に写しを持つようにする と , 仮想配列へのアクセスの多くがバッフ アへのアクセスて済み , 仮想配列へのアク セスは劇的に高速になります。 写しを持っといっても , その実現方法 ( 写 し出す範囲をひとつにするか複数にするか , また写し出す範囲の大きさを何バイトにす るかなど ) によってアクセス速度は変化する て、しよう。 そこて次に , より高速なアクセスを実現 する方法を考えます。写しを置く主記憶上 の領域をバッファ , またアクセスの対象が ページ バッファ ( 4 ページの場合 ) バッファ上に存在する確率をヒット率と呼 びます。速度を上げるにはヒット率を上げ ればよいことになります。逆にヒット率が 低いと頻繁に補助記憶からバッフアへの読 み出しを行わなければならず , 毎回補助記 憶にアクセスするのとほとんど変わらなく なります。仮想配列ライプラリては LRU(Least RecentIy Used) アルゴリズムと 呼ばれるアルゴリズムを用いてヒット率を 上げています。 LRU アルゴリズムは , バッフアをベージ と呼ばれる複数の領域に分割し , バッファ の更新はページ単位て行います。各ページ は仮想配列のおのおの異なる部分を任意に 写し出せます。仮想配列のひとつの部分し かバッファ上に写し出されず , 仮想配列の ふたつ以上の部分が頻繁にアクセスされる ような場合 , 参照ページが変わるたびに補 仮想配列 助記憶へのアクセスが起こりますが , 複数 の部分が写し出されていれば , このような 場合にも高速なアクセスが可能なためバッ フアを複数に分割しています。バッフアに ない対象にアクセスする場合 , いずれかの ページを更新しますが , ページ更新後のヒ ット率の低下を防ぐため , その時点て、時間 的にもっともアクセスされていないページ を更新するようにします。これは参照の局 所性から , 今後しばらくの間アクセスされ る確率がもっとも低いと考えられるのは , そのようなページだからて、す。このため Least Recently Used アルゴリズムと呼ばれま す。 仮想配列ライプラリては , 仮想配列を先 頭からバッフアのページと同じ大きさに区 切り , これを仮想配列のページと呼び , 仮 想配列のページの内容をバッファ上のペー ジに写し出します。 Fig. 2 ては仮想配列の四つのページがバッ ファ上に写し出されています。 こて問題 となるのは , バッフアのページ数と 1 ページ の大きさをどれくらい確保するかてす。 まずバッフアのページ数について考えま しよう。仮想配列のふたつ以上のページを 交互にアクセスするような場合は , バッフ アのページ数を 1 にすることは重大な誤りて す。てはページ数は多いほうがよいかとい うと , 同時に頻繁にアクセスされる仮想配 列のページ数より多いページ数を確保して も主記憶容量のムダてす。 次に 1 ページの大きさについて考えると , 1 ページが小さいと参照ページが頻繁に変わ るのて , 頻繁に補助記憶にアクセスする可 能性が高くなります。しかし主記憶の容量 やページ更新のための補助記憶へのアクセ ス時間の問題を考えると 1 ページをむやみに 大きくてきません。結局 , バッフアの最適 なページ数と 1 ページの最適な大きさは , 各 仮想配列ごとに異なるということてす。そ のため仮想配列ライプラリては , 各仮想配 Conference Room 139