初期化 - みる会図書館


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

1. 月刊 C MAGAZINE 1991年7月号

常駐サイズのコンヾクト化 C 言語で記述された TSR 型テパイスドライバ の , 登録や解除か従来のテパイスドライバよ りも容易であるというメリットを損うことな く , 常駐サイズの肥大化というテメリットを Turbo C / C 十十を利用して解消します。 初期化部分の切り離し Part 2 て示したプログラムて、は near ヒー プ領域とスタック領域を切り離して常駐し ますから , C 言語のプログラムとしては常駐 サイズがわりと小さくなっています。しか し , 常駐時に不要な初期化部分もいっしょ に常駐しています。 C 言語て、はアセンプラ並 みの小細工が難しい部分があるため , 初期 化部分をいっしょに常駐させてしまうのは ある程度仕方がないことかもしれません。 実際 , MS-C て、は , これ以上常駐サイズを切 り詰めることは難しいて、しよう。 しかし , 常駐後に不要な初期化部分を切 り離せられるのならば , 切り離したほうが よいのはいうまてもありません。そこて , 本バートてはさらにもうひとひねり加えて , Turbo C / C 十十ならて、はの手法て、初期化部 分の切り離しに挑戦してみます。 参考文献 [ 5 ] に TSR プログラムて初期化 部分を切り離す例が紹介されています。そ の例を筆者流に整理すると ; 基本的には , ①常駐部と初期化部でソースを分ける ② tcc コマンドのスイッチ - z でセグメント とクラスを指定して Fig. 15 のような配置 にする 58 C MAGAZINE 1991 7 ③スタートアップルーチンが初期化部にリ ンクされるため , EXE ファイルにする といった感じになるてしよう。 Part 2 て、示したプログラムも基本的には TSR プログラムなのて、 , この方針て、初期化 部分を切り離すことが可能なはずて、す。初 期化部分を切り離せばアセンプラと比較し ても , それほど遜色のない常駐サイズにな るはずてす。 Part 2 て、示したプログラムを 書き直して初期化部分の切り離しに挑戦し てみましよう。 初期化部分切り離しの問題点 初期化部分の切り離しは基本的には Fig. 15 のようにすればよいのて、すが , すべてを C 言語て、記述しようとすれば , 主として以下 のような問題点があります。 Null pointer assignment C 言語て、犯しがちなミスのひとつに , 初期 化していないポインタを使用するというミ スがあります。 初期化していないポインタは通常初期値 が 0 てあるため , 初期化していないポインタ て書き込みを行うと , DS セグメントのアド レス 0 付近が壊されます。したがって , 通常 の C 言語の処理系て、は DS セグメントのアド レス 0 から識別コードを設定しておき , 終了 時にその識別コードをチェックして , 破壊 されていれば , 「 NuII pointer assignment 」 というメッセージを表示します。 このチェックはコンパイラが規定してい るセグメントの並びになっているものとし て行われるため (Turbo C の場合は DS レジス タが示しているセグメントの先頭に識別コ ードがあるものとしてチェックする ) , Fig. 15 のようにセグメントの並びを細工してい ると , Null ポインタによる書き込みを行っ ていなくても「 NuII pointer assignment 」と いうメッセージが表示されてしまいます。 CS=DS=SS Fig. 15 は「 CS = DS = SS 」を前提にしてい ます。しかし , スモールモデルのスタート アップルーチンては , Fig. 15 のようなセグ メント配置の場合 ( TEXT±グメントが先 頭にない場合 ) , CS*DS の状態て main 関数をコールしてしまいます (Fig. 16 ) 。これがなぜいけないのかという と ,Fig. 16 のように CS レジスタが初期化部

2. 月刊 C MAGAZINE 1991年7月号

0 プロクラミング研究 特 集 分以降を指していると , 常駐部は別のセグ メントに属することになるため , 常駐部の 関数を呼び出せないからて、す。 C 言語ては CS レジスタの操作はかなりの 難問て、す。 CS レジスタが初期化部分のコード領域を 指していると常駐部のコード領域を呼び出 すことがて、きない。これはセグメントの利 点て、もあり , 欠点て、もあります。 常駐部ではライプラリか 使用できない ライプラリは通常のセグメント配置を前 提にしてコンパイルされているため , Fig. 15 の初期化部分て、ある TEXT, DATA, BSS セグメントにリンクされてしまいま す。したがって , 初期化部分を解放してし まうと常駐部て、はライプラリが使用て、きな くなります。 なお , ライプラリの中には表に出てこな いサプルーチンもあるため , 注意が必要て す。たとえば , Turbo C / C 十十て、は , ①ストラクチャの代入 ② huge ポインタの加減算 ③ long/unsigned long の乗除算 などを行うとライプラリが呼ばれてしまい ます。 Fig. 15 初期化部分を切り離すセグメント配置 下位アドレス —PSP PSP 領域 ( 100h ノヾイト ) コード領域 RESLTEXT セグメントクラス℃ ODE ' テパイスへッダ 初期化されているテータ領域 RESLDATA セグメントクラス℃ ODE ' 常駐部分 ローカルスタック領域 DGROUP 初期化されていないテータ領域 RESLBSS セグメントクラス℃ ODE ' コード領域 クラス℃ ODE ' —TEXT セグメント 初期化されているデータ領域 ー DATA セグメント クラス 'DATA 初期化されていないテータ領域 ー BSS セグメントクラス 'BSS nea 「ヒープ領域 スタック領域 —STACK セグメント クラス 'STACK CS, SS, DS 4 64K バイト以下 初期化部分 上位アドレス Fig. 16 スモールモテルのスタートアップルーチン実行後のセグメント配置 下位アドレス ' 1 ←—PSP SS, DS PSP 領域 ( 100h ノヾイト ) コード領域 RESLTEXT セグメント クラス℃ ODE ' テパイスへッダ 初期化されている領域 RESI—DATA セグメントクラス℃ ODE ' ローカルスタック領域 DGROUP 初期化されていないテータ領域 RESLBSS セグメントクラス℃ ODE ' コード領域 —TEXT セグメント クラス CODE 初期化されているテータ領域 ー DATA セグメント クラス 'DATA 初期化されていないテータ領域 ー BSS セグメント クラス 'BSS near ヒープ領域 スタック領域 —STACK セグメント クラス 'STACK' NuII pointer assignment 対策 これに対する対策はいくつか考えられま 常駐部のコードセグメントの先頭に識別 コードを埋め込む 2 常駐部のデータセグメントの先頭に識別 コードを定義して , 先頭にくるようにリ ンクする ( Fig. 15 のセグメント配置を一 部変更する ) ③タイニィモデルでは「 N ⅶ pointerassign ment 」のチェックが行われないため , タ イニィモデルのスタートアップルーチン を使用する 常駐部分 呼び出せない CS 呼び出せる 初期化部分 上位アドレス 特集 TSR プログラミング研究 59

3. 月刊 C MAGAZINE 1991年7月号

学 五ロ Strict Ref/Def / * i は宣言。メモリは割り当てられない / * j は定義。これ以上 j の定義があってはならない * / Common List 9 List 1 1 extern i nt i : void foo() extern i void f80 / * extern が指定されていても , i の定義になる * / / * つまり , i のためにメモリが割り当てられる / * j も定義である 1 ・ー void bar() / * これらがリンクされると , j が複数回定義されているのでエラーになる * / / * a. c での i の参照は , b. c の i をアクセスする extern i nt void bar() / * これらがリンクされると , i, j それぞれまとめられ , / * 同じアドレスに配置される / * ひとつだけ定義がなければならない / * 工ラー・複数のファイルに定義があってはならない * / / * 同様に i の定義 / * j も定義である Relaxed Ref/Def / * i は宣言。メモリは割り当てられない extern i nt / * j は定義 int j; void List 1 2 lnitialization / * i は初期化されているので定義 / * j は宣言 : メモリは割り当てられない List 1 0 extern i i void foo() * 1 よ int i; void bar ( ) / * これらがリンクされると , j はまとめられ , 同じアドレスに配置される * / / * a. c での i の参照は , b. c の i をアクセスする * 0 / * 少なくともひとつは定義がなければならない / * 複数のファイル中で定義があってもかまわない * / / * 宣言 / * 定義 . メモリが割り当てられ , 初期値が書き込まれる * / / * 初期化されたものだけが定義となる * / 定義に変換されますから , 複数のファイル ならない」という点を使用します。この理由 ログラムが複数のファイルて、構成される に仮の定義が出現することはてきません。 は , ゆるい Ref / Def モデルやコモンのような ときにはヘッダファイルを作成し , その 中に使用する外部オプジェクトの宣言を 複数ファイルに同じ名前の定義が分散して ーポータビリティ extern を指定して入れておき , 各ファイ いる場合 , 多くの環境てはこれらを効率よ ルて # include するのがいいてしよう。 くサポートすることがてきないからだそう てす。 さて , 以上が ANSI 規格が定める宣言と定 なんとも常識的な方法てすが , ポータビ 初期化モデルからは「名前が初期化される 義てす。ては私たちはどのようにオプジェ リティを考えるとこんなものてしよう ( 笑 ) 。 と , それは定義てある」という点を採用して クトを定義し , 宣言すればよいのてしよう ANSI 規格のお墨っきがあるからといって , います。これによって , extern が指定され か。てきるだけ強い制限に合わせておけば extern int i ている名前も初期化されてよく , 名前の定 よいてしようから , 次のようになります。 などと書くと , いくっかの処理系 ( たとえば 義になります。 ANSI 規格の理由書によると ・全プログラム中で定義はひとつだけにす GCC Ver 1.37 ) て、は Warning になります。 「可能なかぎり広範な環境とすてに存在する る 今回は宣言と定義に関してお話しました。 処理系に便宜を図るため」だそうてす。 厳密な Ref / Def モデルからの制限てす。 複雑になってしまったのは ,DennisRitchie ANSI が採用した初期化十厳密な Ref / Def ・定義には extern を指定しない によって採用されたコモンモデル , UNIX の モデルにおいても仮の定義が有効なことに extern のついた宣言を初期化てきない処 cc が採用しているゆるい Ref/Def モデルとの 注意してください。厳密な Ref / Def はプログ 理系があるからてす。 整合性をてきるだけ残しつつ , 多くの環境 ラムを構成する全ファイル中てただひとっ ・定義で初期化する や現存の処理系に対応しようとしたためて の定義しか許しませんが , ひとつのファイ 初期化モデルを採用している処理系ては ル中ての仮の定義は何度出現してもかまい 初期化がないと定義とみなされません。 しかし , いわゆる常識的なプログラムの ません。 ・定義以外 ( 宣言 ) では必す extern を指定する 書き方をしているかぎり , それほど問題に ただし , 仮の定義はファイルの終わりて これも厳密な Ref/Def からの制限てす。プ はならないてしよう。 c 言語雑学講座 97

4. 月刊 C MAGAZINE 1991年7月号

実力養成講座 スタートルプ c + + 回 五ロ 今回も , 前回に続き , C 言語とほぼ同様の 機能である , 変数の記憶クラス , プリプロ セッサ , インライン関数そしてポインタを 中心に解説する。 理論編 龍崎昌平 有効範囲は記述 ( 宣言 ) したところからプロ するために , その変数の有効範囲は関数内 「変第の記憶クラス ックの終了まてとなる (Fig. 1 ) 。 となる。しかし , C 十十ては変数の宣言は式 この自動変数は , プロック内てネストて と同様にどこにて、も記述てきることから , きる 0Fig. 2 に示す例ては関数の先頭て、宣言 変数の記憶クラスは , 変数の有効範囲 ( ス Fig. 1 C 十十の自動変数の宣言 した変数と同様の名前の変数を if のプロック コープ , Sc 叩 e ) と有効期間を決定する。 C プ 関数の型関数名 ( 引数リスト ) 内て、宣言している。 ログラムにおいては , プロックの中て宣言 初期化の行われない自動変数の値 ( 初期 された変数は , そのプロック内て、有効て、あ 値 ) は不定となる。初期化を行う場合は , 次 変数の宣言 り , プロックの処理が終了すると失われる。 文 : の形式によって変数に初期値を代入する。 このように変数がどの範囲て、使用て、きるか 変数の宣言 データ型変数名 = 定数 を区別するのが記憶クラスて、ある。変数の 初期化を行った場合は , 変数の領域が割り 己憶クラスには , C 言語と同様に「自動変 当てられた時点に初期化される。つまり , 数」 , 「レジスタ変数」 , 「静的変数」 , 「外部 プログラムの処理がプロックに達した時点 変数」がある。 に初期化される。ただし , 配列の初期化は 自動変数 ( ローカル変数 ) 行えない。 自動変数は , プロック内て、定義される。 外部変数 ( グローバル変数 ) そして , その変数の有効範囲は定義された 外部変数は , 関数と同じようにプログラ 箇所からプロックの終了まてとなる。プロ ム全体て使用てきる変数てある。この変数 ックの処理が終了すると変数の領域は解放 は , 関数の外側て、定義する。 こて , 定義 される。注意すべきことは , C 言語ては変数 とは変数の領域がメモリ上に割り当てられ は必ず宣言してから使用する。したがって , ることをいう。外部変数の場合は , プログ 関数内て、使用する変数は関数の先頭て、宣言 有効範囲 有効範囲 一三ロ Fig. 2 自動変数のネスト 関数の型関数名 ( 引数リスト ) 変数 a の宣言 if( 式 ){ 変数 a の宣言 116 C MAGAZINE 1991 7

5. 月刊 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)

6. 月刊 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

7. 月刊 C MAGAZINE 1991年7月号

特 Fig. 17 コンノヾイル方法 ① Turbo C 2.0 tcc -c -w -ms -zACODE -zBCODE -zCRESl TEXT -zDRESl BSS -zPDGROUP -zRRESl DATA -zTCODE escpresi. c tcc -c -w -ms -zPDGROUP escpinit. c tcc -c -w -mm -zC TEXT -zPDGROUP escpmain. c 0 プログラミング研究 集 ヘッダ (eatcon. h) 初期化部 (eatconi. c) 常駐部 (eatconr. c) すことが可能てす。ソースリストを , tlink escpresi 十 escpinit 十 %TC%*L 旧 *cOs 十 escpmain, escptc, /c, %TC%*L 旧 *cs ただし % TC % は Tu 「 bo C のディレクトリを示す環境変数で , バッチファイルとして記述している @Turbo C 十十 1 .0 tcc -c -w -ms -k- -zACODE -zBCODE -zCRESl TEXT -zDRESl BSS -zPDGROUP -zRRESl DATA -zTCODE escpresi. c tcc -c -w -ms -zPDGROUP escpinit. c tcc -c -w -mm -zC TEXT -zPDGROUP escpmain. c link escpresi 十 %TC%*L 旧 *cOs 十 escpinit 十 escpmain, escptc, /noi, %TC%*L 旧 *cs; ただし % TC % は Tu 「 boC 十十のティレクトリを示す環境変数で , バッチファイルとして記述してる Table 22 tcc コマンドのスイッチー z ( テフォルトはスモールモテルの場合 ) に示します ( 付録ディスク収録 ) 。 この場合は初期化部て常駐部を呼び出し ていないため , main と far main に分ける必 要はありません。これに関しては説明を省 略します。詳細はリストを参照してくださ い。コンパイルは Fig. 18 のように行ってく スイッチ —zAname —zBname —zCname —zDname —zGname —zPname —zRname —zSname —zTname -zX * 要 コードセグメントクラス = name コードセグメントに対するコードグループ名 = name 非初期化テータセグメントグループ名 = name 非初期化テータセグメント名 =name コードセグメント名 = name 非初期化テータセグメントクラス =name 初期化テータセグメント名 =name X に対するテフォルト名を使用する 初期化テータセグメントクラス = name 初期化テータセグメントグループ名 = name ( テフォルト : CODE) ( テフォルト . BSS) ( テフォルト : TEXT) ( テフォルト : BSS) ( テフォルト : DGROUP) ( テフォルト : DATA) ( テフォルト : DGROUP) ( テフォルト : DATA) 例 -zA * → -zACODE 常駐部でライプラリを 使用する方法 3 Fig. 18 コンパイル方法 ① Turbo C 2.0 link eatconr 十 %TC%*L 旧 *cOs 十 eatconi, eatcon, /noi, %TC%*L 旧 *cs, tcc -c -w -ms -zPDGROUP eatconi. c -zPDGROUP -zRRESl DATA -zTCODE eatcon 「 . c tcc -c -w -ms -k- -zACODE -zBCODE -zCRESl TEXT -zDRESl BSS @Turbo C 十十 1 .0 tlink eatconr 十 eatconi 十 %TC%*L 旧 *cOs, eatcon, /c, %TC%*L 旧 *cs tcc -c -w -ms -zPDGROUP eatconi. c -zPDGROUP -zRRESl DATA -zTCODE eatconr. c tcc -c -w -ms -zACODE -zBCODE -zCRESl TEXT -zDRESl BSS ンの仕様が変更されたため , Turbo C と同 じ順序てリンクすると誤動作します。セグ メントのリンクの順番が問題てあるため , 付属するスタートアップルーチンを参考に して , アセンプラなどてセグメントを定義 したファイルを最初にリンクすれば解決し ますが , 今回は C 言語だけて実現することに ポイントを置いたため , リンクの順番を変 更することて解決しました。 なお , リンカに tlink を使用すると外部参 照が変な値に設定されてしまい , 暴走して しまいました ( たとえば , main 関数のコール が callFIBB のようになります )olink は MS -C 5.1 / 6.0 のものて、確認しました。 TurboC / C 十十て、は基本的にスタートア ップルーチンを最初にリンクすることを前 提にしているため , スタートアップルーチ ンを最初にリンクしない場合はいろいろと 小細工が必要になります。 初期化部分を切り離す CON 出力ファイル化 ユーティリティ Part 1 て紹介した CON 出力ファイル化ユ ーティリティ (eatcon. c, 付録ディスク収録 ) も同じ手法を用いて , 初期化部分を切り離 -DATA, ー BSS セグメントの変数を 使用しないライプラリの使用 先ほどは常駐部て、ライプラリを使用する ことはてきないと説明しましたが , ライプ ラリを切り離さずに常駐させれば , 当然 , 使用てきます。かといって , Part 2 て、紹介 したように , near ヒープ領域の直前まて、常 特集 TSR プログラミング研究 63 変更してもかまいません。 escpmain. c を取り去って far main を main に め , main と far main に分ける必要はなく , なお , TEXT セグメントが先頭にあるた のように行います。 このとき Turbo C てのコンパイルは Fig. 20 いことてしよう。 ライプラリを使用しようとすれば仕方がな ライプラリが常駐してしまうことてすが , トアップルーチンや初期化処理て使用した Fig. 19 のセグメント配置の欠点はスター てもそれほど気にならないと思います。 用していないはずなのて , この制約があっ どが DATA, BSS セグメントの変数を使 作や文字列操作などのライプラリはほとん 能な DOS コールを使用していないメモリ操 ラリしか使用てきません。常駐部て使用可 BSS セグメントの変数を使用しないライプ に示します。ただし , この場合は DATA, 常駐部を少なくするセグメント配置を Fig. 19 駐させるのもおもしろくありませんから ,

8. 月刊 C MAGAZINE 1991年7月号

c, escpinit. c, escpmain. c, escp. h のように 処理て、使用したライプラリのコード部分と -DATA, ー BSS セグメントの変数を 使用するライプラリの使用 すれば , コマンドラインから簡単にヘルプ いうことになります を表示させることがて、きて便利て、す。 また , escpresi. C, escpinit. C, escpmain. 賢明な読者は Fig. 21 のようなセグメント 参考のため , CON 出力ファイル化ユーテ c, escp. h て、は初期化処理の機能を豊富にし 配置にすれば , DATA, BSS セグメント ィリティの常駐サイズの比較を Table 24 に ても常駐しないため , あまり気になりませ の変数を使用するライプラリも使用て、きる ん ( とくにヘルプメッセージを詳細にて、きる 示します。 と思われるてしよう。 初期化部分を切り離すことにより , C 言語 ことは利点て、しよう ) 。デバイスドライバは しかし , スタートアップルーチンが BSS によるデバイスドライバや TSR プログラム CONFIG. SYS の設定を変更しようとして セグメントの先頭から near ヒープ領域の直 の記述がより実用的になります。 も , 通常 , ヘルプ機能がないため , 簡単な 前まて、を 0 クリアしてしまうため , Fig. 21 の Part 2 の手法とは異なり , 本バートて、用 変更て、もいちいちマニュアルを見る必要が セグメント配置は暴走してしまいます。 いた手法はほかの処理系に適用することは あります ( デバイスドライバのオプションス DATA, BSS セグメントの変数を使用す tcc コマンドの イッチはあまり変更することがないため , 難しいて、しよう。とくに るライプラリを使用しようと思えば , 結局 , スイッチ -z に相当する機能がないと不可能 覚えている人は少ないて、しよラ )oescpresi. near ヒープ領域の直前まて、常駐させるしか 方法がないのかもしれません。 Fig. 19 常駐部でライプラリを使用するセグメント配置 常駐サイズの比較 PC ー 9801 版 ESC / P プリンタドライバの escptc. C と escpresl. C, escpinit. C, escpma in. c, escp. h ( 付録ディスク収録 ) の常駐 サイズを比較してみました (TabIe 23 ) 。当 然のことな力すら , escpresi. c, escpinit. c, esc pmain. c, escp. h(Fig. 15 のセグメント配置 , 付録ディスク収録 ) の場合に常駐サイズが約 2K バイトといちばん小さくなっています。 アセンプラて記述すればもっと小さくて、き ますが , この程度て、あれば C 言語て、記述して も , それほど負担にはならないて、しよう (esc presi. c, escpinit. c, escpmain. escp. h ( 付 録ディスク収録 ) て、は初期化処理 ( escpinit. c ) の最初に記述している prninit 関数まて、を常駐 させています ) 。 ライプラリを常駐させる escpresi. c, es cpinit. c, escpmain. c, escp. h(Fig. 19 のセグ メント配置 , 付録ディスク収録 ) の場合て、 も , escptc. c( 付録ディスク収録 ) と比べると 常駐サイズはかなり小さくなっていますか ら , 常駐部て、ライプラリを使用したい場合 には役立ってしよう oescpresi. c( 付録ディス ク収録 ) の例ては , 実際には常駐部がライプ ラリを使用していませんから , Fig. 16 と Fig. 15 の差はスタートアップルーチンと初期化 64 C MAGAZINE 1991 7 下位アドレス ↑ PSP 領域 ( 100h バイト ) コード領域 —TEXT セグメント テパイスへッダ 初期化されているテータ領域 ー DATA セグメント ローカルスタック領域 初期化されていないテータ領域 ー BSS セグメント クラス℃ ODE ' コード領域 爪げー TEXT セグメントクラス℃ ODE 初期化されているテータ領域 ー DATA セグメント クラス 'DATA 爪灯ー DATA セグメントクラス 'DATA 初期化されていないテータ領域 ー BSS セグメント クラス 'BSS INIT—BSS セグメント クラス 'BSS' nea 「ヒープ領域 スタック領域 —STACK セグメント CS, SS, DS クラス℃ ODE 常駐部分 クラス℃ ODE ' DGROUP 64K バイト以下 初期化部分 クラス 'STACK ↓ 上位アドレス ( 常駐部のライプラリが一 DATA ー BSS セグメントの変数を未使用 ) Fig. 20 コンバイル方法 tcc -c -w -ms -zACODE -zBCODE -zPDGROUP -zTCODE escpresi. c tcc -c -w -ms -zClNlT TEXT -zDlNlT BSS -zPDGROUP -zRINIT DATA escpinit. C tcc -c -w -mm -zClNlT TEXT -zDlNlT BSS -zPDGROUP -zRINIT DATA escpmaln. C tlink escpresi 十 escpinit 十 %TC%*L 旧 *cOs 十 escpmain' escptC' /C' O/oTC%*L 旧

9. 月刊 C MAGAZINE 1991年7月号

0 プロクラミング研究 特 といえます。このスイッチ -z はセグメント スイッチはソースリストの頭のほうに書い もしれません。 やクラスの初期値を変更するだけなのて、 , てあります ) 。誌面の関係て、 , この件に関し 自作のデバイスドライバだけて、もコマン コンパイラ側のサポートは非常に簡単なは ては説明を省略します ( ソースリストの解読 ドラインから登録て、きるようにすれば , 多 ずて、す。しかし , デバイスドライバや TSR は難しいかもしれません。ヒントは TabIe 3 重起動て、きない ADDDRV/DELDRV コマン プログラムを記述する際に非常に役立つ機 て、す ) 。要望があれば説明する機会があるか ドを少しはカバーて、きるて、しよう。プロッ 能て、す。ほかの処理系て、もサポートして欲 Fig. 21 常駐部てライプラリを使用するセグメント配置 しいものて、す。 下位アドレス Turbo C / C 十十は MS ー C と比較してオプ ジェクト効率はもうひとつ , といった感が ありますが , コンパイル速度と小回りのき く機能は段違いに優れています。筆者はど ちらかといえば , MS ー C 派て、すが , ・疑似変数 ・インライン展開する関数 (geninterrupt( ) など ) ・インラインアセンプリ言語 などの機能はコンパイル速度とともに Turbo C にかなりの魅力を感じさせます。 本特集て、は , キャラクタデバイスドライ バをコマンドラインから登録する例を紹介 しましたが , ADDDRV/DELDRV コマンド がサポートしていないプロックデバイスド ライバもコマンドラインから登録て、きます。 ただし , 今回紹介した手順よりも , さら に複雑な手順が必要となります。たとえば , グラフィック VRAM のディスクドライバを TabIe 23 PC ー 9801 シリーズ ESC / P プリンタドライノヾの常駐サイズ比較 TSR 型デバイスドライバとして作成すれ 常駐バイト数 ファイル名 コンノヾイラ ば , 必要なときに RAM ディスクを登録し , Turbo C 2.0 2BDOh 不要になれば解除することにより , グラフ Turbo C 十十 1.0 escptc. c 2AFOh ィック VRAM を使用するほかのアプリケー MS-C 6.0 2D70h ションとの共存が可能になります。そのた Turbo C 2.0 81 Oh め , ディスク容量に制限のあるノートパソ Turbo C 十十 1.0 A20h コンて、はわずか 250K バイトて、も結構重宝し Turbo C 2.0 1760h ます。参考として TSR 型プロックデバイス ドライバの例を PC ー 9801 版グラフィック VRAM5-•イスクドライノ *(gvdisktc. c, 付録 ディスク収録 ) 初期化部分を切り離す PC- 9801 版グラフィック VRAM ディスクドライ 常駐部 (gvdiskr. c) 初期化部 (gvdiski. c) ヘッダ (gvdisk. h) に示します ( 付録ディスク収録。 コンノヾイノレ ↑ PSP 領域 ( 100h バイト ) コード領域 —TEXT セグメント クラス℃ ODE ' テパイスへッダ 初期化されているテータ領域 ー DATA セグメント クラス 'DATA ローカルスタック領域 初期化されていないテータ領域 クラス BSS' ー BSS セグメント コード領域 爪ー TEXT セグメントクラスヨ NIT ' 初期化されているテータ領域 爪灯ー DATA セグメントクラス 'INIT ' 初期化されていないテータ領域 クラス 'INIT INIT—BSS セグメント nea 「ヒープ領域 スタック領域 —STACK セグメント 上位アドレス このセグメント配置では一 BSS セグメントから near ヒープ領域の直前までが 0 クリアされるため , 暴走してしまう ( 常駐部のライプラリが一 DATA , ー BSS セグメントの変数を使用 ) CS, SS, DS 常駐部分 DGROUP 64K バイト以下 初期化部分 ↓ クラス 'STACK 5 5 9 1 1 1 escpresl.c escpinit. C escpmaln. C escp. h Table 24 CON 出力ファイル化ユーティリティの常駐サイズの比較 ファイル名 コンバイラ 常駐バイト数 Turbo C 2.0 2F90h Turbo C 十十 1.0 2EDOh MS-C 5.1 34D0h MS-C 6.0 2FBOh Turbo C 2.0 1 190h Turbo C 十十 1.0 13COh eatcon. C eatconr. C eatconi. C eatcon. h 特集 TSR プログラミング研究 65

10. 月刊 C MAGAZINE 1991年7月号

0 プロクラミング研究 特 集 ば簡単なのて、すが , C 言語て、は難問となりま す ( コラム I ) 。したがって , 通常はアセン プラの助けを借りることになります ( コラム Ⅱ ) 。参考文献 [ 4 ] て、は TurboC/C 十十の tcc コマンドのコンノヾイルスイッチー z て、 DATA セグメントのクラスを CODE に変更 することによりデバイスへッダを先頭に持 ってくる例が示してあります。この方法は なかなかおもしろいアイデアだと思います ( 残念ながら TurboC / C 十十以外には適用て、 きません ) 。 筆者の方法はもうひとひねり加えて , Turbo C/C 十十と MS ー C に適用した例を紹 介します。本バートて紹介するデバイスド ライバの構造を Fig. 10 に示します。 Fig. 8 , Fig. 9 と比べてもらえばわかるように DATA セグメントにデバイスへッダが記述 してあるだけて、 , まったく同じ構造をして います。ということは , 本バートて、紹介す るデバイスドライバは C 言語て、普通に記述し てタイニィモデルて、普通にコンパイルして リンクすればよいということてす。 タイニィモテルのセグメント配置 ( Tu 「 bo C/C 十十 ) Fig. 8 下位アドレス 1 CS, DS, SS コード領域 ー TEXT セグメントクラス℃ ODE ' DGROUP 初期化されているデータ領域 ー DATA セグメントクラス 'DATA' 初期化されていないテータ領域 ー BSS セグメントクラス 'BSS' nea 「ヒープ領域 スタック領域 64K バイト以下 —heapbase 上位アドレス Fig. 9 タイニィモテルのセグメント配置 ( MS ー C ) 下位アドレス コード領域 ー TEXT セグメント クラス℃ ODE ' 初期化されているテータ領域 DGROUP ー DATA セグメント クラス 'DATA' 定数領域 クラス℃ ONST ' CONST セグメント 初期化されていないテータ領域 ー BSS セグメントクラス 'BSS スタック領域 nea 「ヒープ領域 64K バイト以下 end 原理 それて、はなぜ Fig. 10 のような構造てデバ イスドライバが記述てきるのてしようか。 MS-DOS は , デバイスドライバを呼び出す にあたって , ポインタは先頭の NUL デバイ スからデバイスへッダの 2 番目のデバイスへ と Fig. 11 のようにたどっていきます。この とき , 次のデバイスへのポインタは far ポイ ンタ ( 4 バイト ) てあるため ( TabIe 6 ) , デバ イスへッダはとくにプログラムの先頭にあ る必要はありません。すなわち , Fig. 10 の ようにデバイスへッダがプログラムの途中 にあってもよいわけてす。そのため , ひと つのファイルて複数のデバイスドライバを 登録することが可能となるわけてす。実際 にデバイスドライバのチェーンをたどって みれば , Fig. 12 のようにデバイスへッダの オフセットアドレスが必ずしも 0 てないこと 特集 TSR プログラミング研究 47 ↓ 上位アドレス Fig. 10 テパイスドライバの構造 ( COM 型 ) 下位アドレス CS, DS, SS コード領域 ー TEXT セグメントクラス℃ ODE テパイスへッダ 初期化されているデータ領域 ー DATA セグメントクラス 'DATA' 初期化されていないテータ領域 ー BSS セグメントクラス 'BSS nea 「ヒープ & スタック領域 DGROUP 64K バイト以下 上位アドレス