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
もに同じように取り出すことがて、きま す。 Fig. 22 nea 「ポインタ概念図 最大 64K (b) セグメントの取得 先頭 す。 far;tk インタは , もともと far なのて、 まず (void far * ) にキャストしま ※セグメントは固定 ポインタは先頭からのオフセット List 24 ポインタのセグメント部とオフセット部を得る #define SEGof(s) ((unsigned)(((unsigned long)((void far * ) s ) ) 〉〉 16 ) ) 5 : #else ー 0FFof(s)) > 〉 4 ) ) #define SEGof (s) ((unsigned) ( (((uns igned long) ( (void huge *)s)) \ 2 : #if defined(LATTICE) ((unsigned) (s)) 1 : #define OFFof(s) 6 : 4 : 3 : 7 : #endif List 25 —peek, ー poke ライプラリ 変化しません。 near ポインタて、あれ ば , 自動的にセグメント部に現在のデ ータセグメントがセットされます。 のように far* インタにキャストするこ とによって , たとえ near•+ インタて、も そのセグメント値を得ることがて、きま す。 16 ビットを取り出すだけて、す。 こまて、来れば後は簡単て、す。上位 うになります。機能概略は TabIe 13 に示し う。作成したヘッダファイルは List 26 のよ よび CS の値を返すマクロを作成しましょ 必要があることが頻繁に起こります。 DS お コードセグメント ( 以下℃ S ク ) の値を知る ータセグメント ( 以下、、 DS 〃と呼ぶ ) および 8086 プログラミングにおいて , 現在のデ 現在のセグメント値の調査 le 12 に示します。 k, poke ライプラリて、す。機能概略を Tab 一般的な形式にしたのが List 25 に示す一 pee 間中の任意のアドレスにアクセスて、きます。 far;tk インタを使用すれば , IM バイトの空 任意の番地のアクセス ます。 こには重要なテクニックが隠され 3 : 6 : 8 : TabIe 1 1 12 : #define _peekb(), 0 ) 11 : #define _peek( s, 0 ) 10 : #define _pokeb(), 0 , c) 9 : #define —poke( s, 0 , c) 7 : #endif 1 : #if defined(LATTICE) 2: ・ #define fptr(seg, ofs) ((vo id far * ) \ (((uns igned long) (seg) くく 4 : #else fptr(seg, ofs) ((void * ) \ 5 : #define (((unsigned long) (seg) くく 16 ) 4 ) (*((char ( * ( (char far * ) far * ) (unsigned)(ofs))) (unsigned)(ofs))) 能 List 24 のライプラリの機能概路 70 C MAGAZINE 1991 11 SEGof(s) OFFof(s) ライプラリ名 機 ポインタ s のオフセット値を得る ポインタ s のセグメント値を得る OFFof, SEGof は nea 「ホインタ , fa 「ポインタの両方に対して使用できる ていますから , 少し詳しく見ていきましょ Tu 「 bo ( 1 ~ 3 行目 ) Turbo て、は疑似レジスタ変数がサポートさ れていますのて、 , もっとも簡単に調査する ことがて、きます。生成されるコードは非常 にコンパクトなものになります。 LS Ⅱ 4 ~ 7 行目 ) LSI て、は , アセンプリインライン関数がサ ポートされていますのて , その機能を利用 することより , 簡単に記述することがて、き ます。生成されるコードはコンパクトなも
応用 C 言語 C の道具箱 指定に間違いがあった場合には一 1 をそれぞ れ返す。 [ 機能 ] ウインドウ pw 内の相対座標 ( x , y ) に文字列 str を表示する。 endwin( ) 関数 (List 5 , CM911103 ) ーー cu 「 ses 画面の終了 [ 書式 ] #include < stdiO. h> mvwprint. C ” config. h ” 1 : #include 2 : 3 : #def ine MYEXTERN extern 4 : 5 : #include く stdio. h> 6 : #include く string. h> 7 : #include く dos. h 〉 8 : #include く jctype. h> 9 : #include く jstring. h 〉 10 : 11 : #include 'window. h ” 12 : 13 : #ifdef PROMYPE ” cboxprot. h ” #include 14 : 15 : #endif 17 : mvwpr i ntw (MWI NDOW *PW, 18 : int y, 19 : int X, unsigned char str ロ ) 20 : 21 : ( 22 : int len, i, *. e 23 : static unsigned pos = 0 24 : char k [ 4 ] , attribC4] ; 25 : uns i cdmemseg 26 : unsigned cdmemoff ・ 27 : unsigned atmemseg 28 : uns i gned atmemof f 29 : unsigned xlen ・ 30 : ylen ・ 31 : 32 : iny ; 33 : ylenmax 34 : char attr ; 35 : unsigned scrollwid ・ 36 : scrollwid = (unsigned)pw->scroll ・ sclwidck(&scrol lwid) ; 38 : attr ニ (char)pw- 〉 gattrib; 39 : 2 ) : xlen ニ田ー〉 xlenn ー (KEIWIDLR * 40 : ylen = pw->ylenn ー (KEIWIDTD * 2 ) : 41 : ylenmax = ylen * scrollwid ー 1 42 : 43 : if (x 〉 = 80 Ⅱ x ←ー 1 Ⅱ y 〉ニ Ⅱ y 1 enmax 44 : y ← return(-l) ; 45 : 46 : inx = x ー KEIWIDLR; 48 : iny = Y ー KEIWIDLR; POS = (iny * xlen + inx) * 2 49 : 50 : cdmemseg = FP—SEG(pw->psavecode) ; cd11Emoff = : atmemseg = FP—SEG 田ー〉 psaveattr ・ 52 : 53 : atmemoff = FP—OFF pw->psaveattr ・ 54 : len = strlen(str) : 55 : for (i=0; i く le れ ; i + + ) { 56 : if( iskanji( str[i] ) ) ( = (int)jmstojis( str [ i + + ] * 0X100 + strCi] 58 : kan 0 = (char) ( ( code 〉〉 8 ) ー 0 0 ) : 59 : kan 1 = (char) ( code & 0xff ) ・ > 〉 8 ) ー 0X20 ) kan 2 = char kan 3 = char & 0xff)1 0X80 ) 62 : attrib[0]=attribC2]=attr : mypoke cdmemseg, moff + pos , kan , 4 64 : mypoke atznseg, atmemoff + POS' attr i 4 65 : 66 : pos + = 4 ・ else{ 68 : 69 : pos = (pos /(2*xlen) + 1)*(2*xlen) ; 70 : i 十十 else if( str[i]=='}n' ) { 73 : pos = (pos /(2*xlen) + 1)*(2*xlen) ; 74 : else{ - strCiJ ・ 78 : attribC0]= attr ・ 79 : mypoke (cdmemseg, cdmemoff + POS' kan , 2 ) 80 : mypoke (atmemseg, atmemoff + POS. attrib' 2 ) 81 : 82 : 83 : 84 : 85 : List 4 #include "pldwn. h" #include window. h #include curses. h endwin ( ) [ 引数 ] なし [ 戻り値 ] なし [ 機能 ] initscr て、定義した仮想画面 stdscr を削除す wmove( ) 関数 (List 6 , CM911104 ) ーー仮想画面でのカーソルの移動 [ 書式 ] #include く stdio. h> #include < string. h > window. h" #include wmove (MWINDOW * pw, int y, int x) [ 引数 ] * pw ←ウインドウ関連のパラメータを格 納する構造体のポインタ y ←カーソルの表示位置 ( y 座標 ) x ←カーソルの表示位置 ( x 座標 ) [ 戻り値 ] 正常終了の場合は 0 を返す。 [ 機能 ] ウインドウ pw において , カーソルを (), y) に移動する。 応用 C 言語 107
新 MS ー DOS プロクラミンク入門い C プロクラマのための 常駐サイズ 0 を実現する子プロセスのオーバレイ関数 List 分断が原因て、す ( 参考文献 [ 1 ] にこの現象 の原因と対策を紹介しています。この現象 て、お悩みの方は参考にしてください ) 。 それて、は , オーバレイ動作を行う exec 関 数て、 ADDDRV コマンドを起動しても , 空メ モリが分断されてしまうのはなぜて、しよう か。実は , exec 関数はコマンドラインから 起動したのと一部異なる動作を行うため , Fig. 6 のように空メモリが分断されてしまう のて、す。 具体的には ADDDRV コマンドは EXE ファ イルヘッダて、空メモリの最上位ヘロードす るような指定になっていますが , exec 関数 はほかの通常のプログラムと同様に下位へ ロードするため , ADDDRV コマンドはデバ イスドライバを自メモリプロックの次にロ ードしてしまいます。 exec 関数による ADD DRV コマンドの起動を Fig. 2 からの続きと して示すと , Fig. 8 のようになります。 コマンドラインからの起動ては Fig. 9 のよ うな動作になりますから , exec 関数て、起動 すると空メモリが分断されてしまうわけて、 す。 exec 関数が EXE ファイルヘッダの指示 どおりに ADDDRV コマンドを空メモリの最 上位にロードすれば空メモリは分断されな くなるはずて、す ( 上記の動作は exec 関数のバ グて、はなく , 正式な仕様て、す ) 。 子プロセスの オーバレイ関数の作成 それて、は ADDDRV コマンドを子プロセス として起動しても空メモリを分断しない関 数を作成してみましよう。 List 3 がその関数 て、 , 基本的には Fig. 2 の手順を踏んて、いま す。インラインアセンプリ言語を使用して いるため , MS-C Ver. 6 . 0 , Turbo C Ve r. 2.0 / C 十十 Ver. 1.0 にしか対応していま せん ( これらのコンパイラを所有されていな い方は LSI C ー 86 Ver. 3.2 試食版にもインラ インアセンプリ言語がありますから , 挑戦 してみてください ) 。 List 3 の説明をする前に , List 4 のテスト 1 : 常駐サイス・ 0 を実現する子フ。ロセスのオーハ・レイ関数 DOS 3. x ) Copyright (C) N. Nakashima 成 1 1.03.24. * 4 : ファイル名 : execovl. c 6 : 7 : ハ・一シ・ヨン 0.01 8 : 1991.03.24. execovl. asm から移植 9 : 10 : コンハ。イル・スイッチ 11 : 12 : Microsoft (R) C CompiIer Version 6. 00A 13 : 14 : cl -c -J -W3 -Gs -Zpl execovl. c 15 : 16 : Turbo C Version 2.0 / TurbO C + + Version 1.0 17 : 18 : tCC —C ー響—B execovl. C 19 : 21 : 22 : #include く string. h 〉 23 : #include く stdlib. h> 24 : #include く process. h> 25 : #include く dos. h> 26 : 27 : #ifdef _TURBOC 28 : #define _dos freemem freemem 29 : #define _asm asm 30 : 31 : extern VOid _restorezero( VOid ) : 32 : #endif 33 : 34 : #ifndef MK FP 35 : #define MK FP(), 0 ) ((void far * ) (((unsigned long) (s) くく 16 ) ー (unsigned) ( 。 ) ) ) 36 : #endif 37 : 38 : #define PRGEND 0x0a / * フ。ロク・ラム終了アト・レス ( INT 22h ) の北。ー / * 親フ。ロセスの PSP セク・メント 39 : #define PARSEG 0X16 40 : #define ENVSEG Ox2c / * 環境変数領域のセク・メント 41 : #define FILETBL 0X34 / * ファイル・テーフ・ルへのホ。インタ 42 : #define FCBI / * PSP 5CH の FCB 1 0x5c 43 : #def ine FCB2 / * PSP 6CH の FCB 2 0x6c 44 : 45 : typede f s truct ( / * MCB 構造体 / * メ刊管理用のマーク ( 'M' or 'Z' ) * / 46 : char mark; / * OWNER ID 47 : unsigned owner; / * 使用メ刊のハ。ラク・ラフ数 48 : mcbsiz ・ mc 団亡 11 ] ; 49 : char 50 : } SMCB; 51 : long huge *PS, unsigned parasiz ) 52 : static VOid paracopy( long huge *Pd, 53 : { / * ハ。ラク・ラフ単位のコヒ。ー while ( parasiz- 54 : 55 : * 図十十 = *ps 十十 ; 56 : * 91 十十 = *ps 十十 ; * 図せ十ニ *ps 十十 ; 58 : * 図十十ニ *PS 十十 ; 59 : 60 : } 61 : 62 : int exec—ovlay( char char cmdlineC] ) 63 : 64 : { 65 : static struct { 66 : unsigned env—seg; char far *cmd_adr ; 68 : char far *fcbl_adr; 69 : char far *fcb2 adr; unsigned spinit' ssimt; *. ipinit, csinit; } paramblk; 73 : union REGS iregs : struct SREGS segs; SMCB far *pmcb; / * PSP 8 囲のコマント・行 char c 80h [ 128 ] ; / * コマント・・ラインの長さ register int cmd len; / * ストラテシ・ unsigned strategy; unsigned newseg, envseg, newsiz, envslz; 80 : / * コマント・・ラインの作成 * / 81 : 82 : if ( ( cmd len = strlen( cmdline ) ) 〉 126 ) 83 : return て一 1 ) ; / * コマント・ラインが長すぎる 84 : / * コマント・・ランコヒ。ー 85 : strcpy cmd80h + 1 , crndline ) ; cmd80h ( c 80h [ 0 ] = ( char )cmd_len ) + 1 86 : / * 新領域作成 for メモリ分断対策 * / 88 : 89 : iregs. x. ax ニ 0X5800 ; intdos( &iregs, &iregs ) ; 91 : strategy = iregs. x. ax; 93 : iregs. X. bx ニ 2 ; 、ノ、ノ タるる わすす 対対 るにに 対 ハ。 ・・テテ値値 ー 4 ・、ノ 外すす期期 渡渡 初初 動動のにに 起起・のの 00 〒 1 境 A. 、・ / * ストラテシ・の取得 / * ストラテシ・保存 / * 上位メモリ・フ・ロックから割付け 新 MS-DOS プログラミング入門 89
00 ライフラリ開発技 マクロ利用で処理系間 の差異を吸収 マクロて、も , 引数の型チェックや型変 換を行うことがて、きる。特定の型を使 いたいのてあればキャストを利用せよ 最初に予告していた , List 3 のプログラム の問題点て、ある処理系独自のライプラリに ついて考察しましよう。 ティレクトリ検索ライプラリ List 3 は , 現在のディレクトリ上のファイ ル名をすべて表示するプログラムて、した。 LAT 独自のライプラリて、ある dfind, dnext 関数を使用しています。 同等なプログラムを MS / QC て、記述すると ヘッダファイル中での 関数定義 COLOR は , LOCATE などに比べて複雑 て、 , マクロて、は実現て、きないのて、 , 関数と して実現しています。このヘッダファイル を利用するプログラムが , 単独のソースフ ァイルて、のみ作成される間は問題ありませ ん。しかし分割コンパイルを行い , 複数の ソースファイルて、このヘッダファイルをイ ンクルードすると , リンク時にエラーとな ります。なぜならば , 各ヘッダファイル中 に COLOR 関数の定義が埋め込まれるからて、 す (Fig. 15 ) 。 本来はこのような関数はヘッダファイル て、定義するのて、はなく , ライプラリファイ ルに登録すべきて、す。 しかし何らかの事情によって , どうして もヘッダファイル中に記述したい場合は , static を指定して内部リンケージを持たせる 必要があります。 static をつけることによ り , 各ソースファイル中に埋め込まれた CO LOR の名前は , 外から、、見えなくクなるの て、 , 衝突が起こることはありません。 List 望洋ライプラリ <lib*dir. h> より抜粋 1 : #if defined(—STDC—) Ⅱ (LSI-C) #define _CdecI 3 : #else #define _CdecI cdecl 5 : #endif 6 : 7 : #if !defined(_LIB_DIR_DEF_) 8 : #define _LIB_DIR_DEF 9 : 10 : #if defined(—TURBOC_) #include 11 : く dir. h> 12 : #elif defined(_POWERC) #inc lude く direct. h 〉 14 : #else # include く dos. h> 16 : #endif 18 : #define DIR_CLOSE 0X80 19 : #define DIR_REMAIN 0X40 20 : #define DIR_CONF 0X20 22 : #define Wi ldcards 0X01 23 : #define Extension 0X02 24 : #define Fi lename 0X04 25 : #define Directory 0X08 26 : #define Drive 0X10 28 : #define MaxPath 80 29 : #define MaxDrive 3 30 : #define MaxDir 66 31 : #define MaxFile 9 32 : #define MaxExt 5 33 : 34 : typedef struct { reservedC21] ; char 35 : 36 : Char attrib; unsigned time; 38 : uns i gned date; 39 : long S1ze; name[13] ; 40 : char 41 : } DIR; 42 : 43 : # if defined(_TURBOC_) Ⅱ (_POWERC) #define DIRfirst(), f, a) (unsigned)findfirst(p, (struct ffblk * ) (f), a) #define DIRnext(f) 45 : (unsigned)findnext((struct ffblk *)(f)) 46 : #elif defined(LATTICE) #define DIRfirst(), f,a) (unsigned)dfind((void *)f, p,a) #define DIRnext(f) 48 : (unsigned)dnext((void *)f) 49 : #el if !defined(_ZTC_) #define DIRfirst(), f, a) _dos_findfirst(), a, (struct find_t *)(f)) 50 : #define DIRnext(f) —dos—findnext( (struct find_t * ) (f)) 52 : #endif 53 : 54 : #endif / * Power C * / / * MS-C, Quick C and Lattice C * / ヘッダファイルには関数定義を埋め込 まない やむを得ず関数定義を記述する必要 がある場合は , 必ず static 記憶クラス指 定子をつけなければならない 「やむを得ず」とは , どのような場合て、あ るかは , 後て、解説します。 特集ライプラリ開発技法 61
ライフボート lnformation from C0mpiler Makers Lattice C レジスタ変数を使用したループ内で アセンプラルーチンを呼び出すプログラム 退避しなければなりません。 Q ⑤その他のレジスタ (SS, SP, C C 言語とリンクするアセンプリ ルーチンを書く場合 , 退避する必 S, (P) は , 意識的に退避する必要 要があるレジスタはどれですか ? はありません (CS, IP レジスタは , call 命令て自動的に退避されます A ①必ず退避する必要があるレ し , SS, SP レジスタは , 基本的に ジスタは , BP, DS レジスタてす。 は , プロセスの途中て値が再設定 ②フラグレジスタは全体をセープ されることはないはずて、す ) 。 する必要はありません。ただし , PC ー 98 シリーズのハイレゾモー D フラグ ( ディレクションフラグ ) だ けはクリアされていなければなり ドのグラフィック VRAM や FM R シ ません。したがって , アセンプラ リーズの VRAM に poke 関数を使っ て、作成したルーチンて , STD を使 て値を書き込むと正しく動作しま 用した場合には , リターンする前 せん 0Ver. 4.0 では , うまくいって に , 必ず CLD を実行して , D フラグ いたのですが , どうしてですか。 をクリアしてください A peek, p 。 ke 関数の内部処理 ③レジスタ変数を使用していなけ て、 , Ver. 4.0 まては , レヾイトずっ れば , SI, DI レジスタは退避しな 転送していましたが , Ver. 4.1 て くても影響ありません。レジスタ は , 2 バイトずっ , rep 命令を使っ 変数を使用している場合には , レ て転送するようになり , 処理が高 ジスタ変数に SI , DI レジスタが割 速化されています。また , 転送バ り当てられていますから , そのレ イト数が奇数の場合には , 1 バイト ジスタ変数の値が壊される可能性 だけ別に転送し , 残りを 2 バイトず があり , SI, DI レジスタは退避す つ転送しています。 る必要があります。たとえば , ル VRAM などのハードウェアが関 ープ変数にレジスタ変数が使われ 係しているア、ドレスに対して , ワ ていて , そのループの中て呼ばれ ード単位 ( 2 バイト単位 ) てアクセス ている関数が SI , DI を退避してい すると , 正しく読み書きて、きない ないと , そのループを無限に回り 機種があります。そのため , pee 続けたり , 逆にループせずに , す k, poke 関数て, 正しく VRAM の OS ファンクションコールを使用て、 ぐに抜け出てしまうというような す。このパラメータを指定しない データを読み書きて、きなくなるこ と , 次にロードされるタスクは , きるように , ファンクションコー ことが起こります。オプション -g ルをエミュレーションしています 現在のタスクの直後にロードされ とがあります。 List 3 のようなマク ej を指定すると , 関数呼び出しの前 が , メモリアロケーション要求 (M に , SI, DI レジスタを待避します 口または関数を定義して , 回避し てしまうのて、 , 現在のタスクがヒ S ー DOS ファンクション 48H ) に対し ープメモリを要求てきなくなりま から , 関数内て SI , DI レジスタを てください ては , Lattice C V86/RT はアロケ す。 待避していないルーチンても , Li Lattice C V86 / RT を使う場 ヒープメモリを使用するタスク ーションするメモリ領域をプロテ st 1 の例のように List 2 のルーチン は , あらかじめ使用するメモリ量に を呼び出すことがてきます。 クトメモリ上に割り当てます。 合わせて , パラメータ「 last seg = 」 ルに何か制限がありますか ? ④ AX, BX, CX, DX, ES レジス 1 タスク当たりに確保するメモリ を設定してください タは , 退避する必要がありません。 については , EDS. CTL のタスクパ Lattice C V86/RT は , Latti ただし , Ver. 3.2 以前の場合 , S, ラメータ「 last_seg = 」て指定する ce C/RT 同様 , 各タスクが MS-D P モデルのときは , ES レジスタは ことにより変更することがてきま List 1 1 : main() 3 : register lllt i : 4 : int register j; 5 : 6 : for(i = 0 ; i く 10 ; i + + ) for(j = 0 ; j く 10 : j + + ) { 7 : 8 : subr(); printf( ” i 9 : = Xd*tj = Xd*n ” 1 , j); 10 : 11 : ) Q SI, 団レジスタを退避しないアセンプラルーチン 1 : TEXT segment public word ' CODE' 2 : as sume cs : TEXT 3 : publ ic subr 4 : subr proc near 5 : mov Si, 0 6 : mov di,Si 7 : re t 8 : subr endp 9 : TEXT ends 10 : end List 2 List 3 peek, poke 関数のマクロ定義 1 : / * poke,peek マクロ定義 ( 関数として定義して、一緒にリンクする方法もあります ) * / 2 : #define poke(seg, off, ptr, size) { \ 3 : union { struct (unsigned 0 , (;) char far *p; } xp; \ 4 : int i; unsigned char far *P = ptr;# 5 : xp. w. 0 = Off; xp. w. s : seg; for(i 0 : i く size; i + + ) *xp. p + + 6 : #define peek(seg, off, ptr, size) ( \ 7 : union { struct {unsigned 0 , s; } w;unsigned char far *p; } xp; \ 8 : int i; unsigned char far *P = ptr;# 9 : xp. 既 0 = Off; xp. w. s = seg; for(i Q A 159 lnformation from Compiler Makers
00 ライフラリ開発技法 て , そのセグメントおよびオフセットを求 64K バイト以内の空間を表すためのものて、 V 日 AM のアクセス めることがて、きます。機能概略を TabIe 11 す。てすから , ある固定されたセグメント PC ー 9801 のノーマルモードて、は , 0XA000 に示します。 に対して , その先頭からのオフセットしか 0 番地からテキスト VRAM に割り当てられて Fig. 23 て、解説をしましよう (List 24 は LA 持ちません。たとえば near* インタが 0X12 います。 1 文字が 1 ワード ( 漢字を除く ) に対 T て、も動作しますが , 34 という値を持っているとしても , どこを こて、は LAT 以外の 応しています (Fig. 2 の。 基準 ( セグメントの先頭 ) としての値かがわ 処理系についてのみ解説します ) 。 List 23 は PC ー 9801 のノーマルモードて、 , からなければ , その物理アドレスを求める テキスト画面上一杯に 80X25 個文字 A を ことはて、きません。 (a) オフセットの取得 表示するプログラムて、す ( 文字の属性につい そこて、作成したのが List 24 のライプラリ (unsigned) にキャストすることによ ては考慮していません ) 。 ( ヘッダファイル ) て、す。このマクロにより , り , 下位 16 ビットを取り出すことにな , こて物理アドレス 0XA0000 を表現する方 near;tk インタ , far ポインタの両方に対し ります。 near;tk インタ , far;* インタと 法が , LAT とそのほかの処理系て、異なるこ テキスト画面一杯に ' A ' と表示 ( PC ー 9801 ) とに注意してください。 Fig. 21 に示してい るように , LAT て、は 16 進 5 桁の物理アドレス 1 int main(void) をそのまま far;* インタにキャストすればよ int 3 いのて、すが , それ以外の処理系て、は , セグ unsigned far *vram = (unsigned far * ) 4 #if defined(LATTICE) 5 メント 4 桁・オフセット 4 桁の合計 8 桁て、表し 0XA0000 : 6 #else 7 たアドレスを far;* インタにキャストする必 0XA0000000 : 8 #endif 9 要があります。 10 for (i = 0 ; i く 80 * 25 ; i + + ) Ⅱ 12 *vram 十十 return( の ; 13 List 23 LAT とそれ以外の処理系て、は far ポイン タの表現法が違う Fig. 20 テキスト VRAM ( 文字部 ) の構成図 ( 80 , 1 ) 0XA0000 ポインタとアドレス 次に , 与えられたポインタから , 実際の 物理アドレスを得る方法を考えましよう。 多くの処理系て、 FP OFF, FP SEG というラ イプラリが用意されています。しかしこの ライプラリは , ・処理系によってアドレス算出法が異なる ・処理系によっては far ポインタに対しての み正しく動作し , near ポインタでは意味 をなさない という致命的な欠点があります。 そこて、ライプラリを作成します。 far ポイ ンタて、あれば , セグメントオフセットは何 とかして求めることがて、きそうて、す。しか し near ポインタについてはどうて、しよう か。 Fig. 22 に示すように , near ポインタは ※ 1 文字が 1 ワード ( 2 バイト ) に対応 ( 80 , 25 ) ( 1 , 25 ) Fig. 21 0XA0000 . 0000 番地を指す fa 「ポインタ (void far * ) 0XA0000 LAT (void far * ) 0XA0000000 LAT 以外の処理系 特集ライプラリ開発技法 69
List 3 プログラムの実行の様子を Fig. 10 に示しま す。これを見れば空メモリを分断すること なく , ADDDRV コマンドを子プロセスとし て起動て、きていることがわかります。 子プロセスのオーバレイ 関数 (exec-ovlay) List 3 の exec 0 ⅵ ay 関数の動作を簡単に 説明します (Fig. 11 ) 。 int 型の関数て、すが , 正常動作の場合は呼び出し元に戻ってきま せんから , 呼び出し元に戻ってきた場合に は何らかのエラーが発生したことになりま す。 ①引数から次の形式のコマンドラインを作 成します。 if ( strategy ! ニ 1 ) 94 : / * メモリの割付け方法反転 95 : i regs. X. bX strategy; / * スト万シ・の設定 96 : ニ 0X5801 : lregs. X. ax intdos( &ire s, &iregs ) ; pmcb = MK_FP _psp ー 1 , 0 ) : / * 現領域の MCB セク・メント 98 : / * 自フ。ロセスの必要ハ。ラク・ラフ数 * / 99 : pmcb- 〉 mcbs i z ; news 1 Z pmcb = MK_FP( * ( unsigned far * ) FP( psp, ENVSEG ) ー 1 , 0 ) : 100 : - / * 環境変数領域の必要ハ。ラクラフ数 * / 101 : envsiz = pmcb—〉 mcbsiz; 102 : #ifdef MSC_VER dO 01100000 00 i & 00 、 eg / * 新環境変数領域の確保 * / 103 : if ー dos allocmem newsiz, &newseg ) / * 新フ。ロク・ラム領域の確保 * / 104 : 105 : #endif 106 : #ifdef TURBOC if ( 01100000 00 iz, & 00 eg ! = / * 新環境変数領域の確保 * / 107 : Ⅱ al locmem newsiz, &newseg ! = / * 新フ。ロク・ラム領域の確保 * / 108 : 109 : #endif 110 : 111 : iregs. X. bx ニ strategy; 112 : iregs. x. ax ニ 0X5801 ; intdos &iregs, &iregs ) ; 113 : return ー 1 ) ; 114 : 115 : 116 : iregs. X. bX strategy; 117 : = 0X5801 ; lregs. X. intdos( &iregs, &iregs ) : 118 : 119 : paracopy( MK-FP envseg, 0 ) , 120 : MK_FP * ( unsigned far * )MK FP( sp, ENVSEG ) , 0 ) , 121 : / * ー新領に環境変数をコヒ。ー envsiz ) ; 122 : paracopy( MK—FP( newseg, 0 ) , MK FP( —PSP, 0 ) , newsiz ) : 123 : / * 新領域にフ。ロク・ラムを北。ー 124 : if ( _dos_freemem( * ( unsigned far * )MK FP( ENVSEQ ) ) 125 : / * 現環境嶺域の解 return( ー 1 ) ; 126 : 127 : * ( unsigned far * )MK—FP( newseg, ENVSEG ) = envseg; 128 : / * 新環境変数領城のセク・メント 129 : * ( unsigned far * )MK—FP( newseg, FILETBL + 2 ) = newse ・ 130 : / , ファイル・トフ・ルの第イノタ北。ー 131 : pmcb = MK_FP( envseg ー 1 , 0 ) ; / * 新環境変数領城の MCB セク・メント * / 132 : / * 0wner ID 変更 for 解放 133 : pmcb—>owner 辷 newseg; pmcb = MK_FP( newseg ー 1 , 0 ) : / * 新領域の MCB セク・メント 134 : / * 0wner ID 変更 for 解放 135 : pmcb—>owner = newseg; pmcb = MK_FP( _psp ー 1 , 0 ) ; / * 現領城の MCB セク・メント 136 : / * 0wner ID 変更 for 解放 137 : pmcb—>owner ニ newseg; / * 新 PSP 領域のセク・メント 卩 138 : iregs. X. bX ニ newseg; / * 新 PSP アト・レスのセット 139 : iregs. h. ah = 0X50 ; intdos( &iregs, &iregs ) ; 140 : 141 : #ifdef _MSC_VER / * C ライフ・ラリ終了処理 142 : -cexit() ; 143 : #endif 144 : #ifdef _TURBOC torezero て ) ; / * 割込へ・クタ 00h 他のリストア 145 : き 146 : #endif TURBOC ) 147 : #if defined( _MSC VER ) Ⅱ defined( / * 新領城の PSP セク・メント 148 : dx, newseg asm mov 149 : _asm sub dx, _psp ax, ds asm mov 151 : —asm add ax,dx asm mov ds,ax 153 : asm fflOV ax, S S 154 : _asm add ax,dx 155 : asm mov ss,ax 156 : mov ax, CS 157 : _asm add ax, dx 158 : asm push ax 159 : #end7f 160 : #ifdef MSC VER 161 : ax, offset newadr asm mov 162 : _asm push ax 163 : _asm retf 164 : newadr: 165 : #endif 166 : #ifdef TURBOC ax, offset $ + 5 167 : asm mov 168 : _asm push ax emit—( 0xcb ) ; 169 : 170 : #endff 171 : / * 新領域のフ。ロク・ラム 172 : 173 : if ( dos freemem( _psp ) ) / * 旧メ刊・フ・ロックの解放 174 : -öxit て一 1 ) ; 175 : 176 : —PSP = newseg; segread ( &s egs ) : / * 環境セク・メント・アト・レス 178 : parambl k. env_seg paramblk. cmd adr = MK FP( segs. ss, c 80h ) : 179 : / * PSP 80H のコマント・行に対するホ。インタ * / 180 : 3 *. fcbl_adr = MK FP( _psp, FCBI ) ; 181 : / * PSP 5CH に渡すテ・フォルト FCB に対するホ。インタ * / 182 : Enrambl k. fcb2—adr = MK_FP ( _ps p, FCB2 ) ; 183 : / * PSP 6CH に渡すテ・フォルト FCB に対するホ。インタ * / 184 : ハ。ス名の位置 185 : iregs. x. dx = FP—OFF( ) ; / * ds :dx ー ( unsigned )¶mblk; / * es:bx ハ。ラメータ・フ・ロックの位置 * / 186 : iregs. X. bX / * スト万シ・を元に戻す / * メ刊不足 / * ストラテシ・を元に戻す コマンドライン文字列 CR ↑ CR を除く文字数 ( 1 バイト ) これらの文字列は最大て、 128 バイトのた め , 起動コマンドのオプション文字列 (c mdline) は 126 バイト以下て、なければいけ ません。 ② DOS ファンクション 5800h ( Table 1 ) て、現 在のアロケーションストラテジを取得し , DOS ファンクション 5801h ( Table 1 ) て、現 在と反対のアロケーションストラテジに 設定します。 DOS のデフォルトて、は最下 位の空プロックから割り当てる設定にな っているため , 最上位の空プロックから 割り当てるように変更することになりま す。 ③自プロセスの環境変数領域とプログラム 領域のそれぞれをコピーするための領域 を dos allocmem 関数 (MS-C Ver. 6.0 ) , allocmem 関数 (Turbo C Ver. 2.0 / C 十十 Ver. 1.0 ) て、穫得します。これらの メモリプロックは通常 , 最上位の空プロ ックから割り当てられます。 ④ DOS ファンクション 5801h てアロケーショ ンストラテジを元に戻します。 ⑤新領域に環境変数とプログラムをコピー / * 新 DS / * 新 SS / * 新 CS / * 新 IP / * 新領域にシ・ヤンフ。 / * 新 IP / * retf : 新領域にシ・ヤンフ。 90 C MAGAZINE 1991 11
応用 C 言語 C の道具箱 能性があるからて、ある。 今回紹介した utextke4 関数は , 環境変数 TERM を使用している。この TERM は以下 のように、、 nec98 ′′をセットする必要がある。 A >set TERM =nec98 List 7 10 : #ifdef PROTOTYPE 11 : #include ” cboxpro t. h" 12 : #end i f 13 : 14 : wprintw(MWINDOW * , 15 : unsigned char strC]) 17 : uns i gned cx 18 : unsigned cy 19 : 20 : CX ー pw—>curx 21 : cy = pw->cury 22 : mvwprintw(pw, cy, cx, str) ; 23 : re turn ( の : 24 : } コンバイル 本プログラムは , TurboCVer. 2.0 およ び MS-CVer. 5.1 て、コンパイルが可能て、あ る。いずれの場合にも各プログラムは分割 コンパイル用なのて、 , コンパイル後オプジ ェクトファイルを読者のライプラリに追加 し , 最後にメインプログラムにリンクする。 Turbo C Turbo C て、コンパイルする場合には , コ ンパイルスイッチを以下のように設定する とともに , Turbo C コンパイラのディレク トリ下の INCLUDEi-•ィレクトリの中に config. h として以下の内容を入れておく必要 がある。また統合環境の中て、コンパイルす るのて、はなく , TCC を使用する。 tcc -v -K -J -w -ml -c ( デバッガを使用 /char を unsigned cha r にする / 漢字を使用する / 警告あり / ラー ジメモリモード / リンクは行わずコンパ イルのみ行う ) く Turbo C 用 config. h > #define TURBOC #ifndef MSDOS #define MSDOS #endif #ifndef PROTOTYPE #define PROTOTYPE utextke4. c List 8 1 : #include ” config. h ” 2 : 3 : #define MYEXTERN extern 4 : 5 : #include く curses. h 〉 6 : #include く string. h> 7 : 8 : utextke4(win, xl, YI, x2, Y2, attrib) 9 : WINDOW *win; 10 : int xl, yl, x2, y2 ・ 11 : chtype attr ib ; 12 : { 13 : int i int n; 14 : 15 : char line ・ 16 : char yok0 [ 128 ] : 17 : char sx [ 128 18 : char tateC4 19 : char upperleftC4] ・ 20 : char upperr ight [ 4 」 ; 21 : char lowerleftC4]; 22 : char lowerrightC4] : 23 : char *terrnname; 24 : int xn, yn, X wattron(win, attrib) ; 25 : termname = getenv( ” TERM") ; 26 : if(! strncmp(termname, ” nec98 strcpy (SX, 28 : strcpy(tate, ”げ ) ; 29 : strcpy(upEErleft , " 「 ) ; 30 : strcpy 。 P80 。 i ght, 工 strcpy(lowerright, " 」 " ) ; 33 : 34 : else{ 35 : strcpy(sx, 36 : 37 : S trcpy tate, 38 : s trcpy upper 1 eft 39 : S trcpy upperr ight, 40 : strcpy lowerleft strcpy lowerright, 42 : 43 : 44 : 45 : 46 : 48 : 49 : 50 : 51 : 52 : 53 : 54 : 55 : 56 : 58 : 59 : 60 : 61 : xl = 2 * ( xl / 2 ) + 1 ・ x2 = 2 * ( x2 / 2 ) + 1 ・ if()l = x2 & & yl = y2) return ( の・ if()l > x2)( i swap (&xl , &x2) ; if()l 〉 y2){ iswap(&yl, 2 ) ; if ()l ! = x2 yl ! = y2 ) goto ・ if ()l = x2 ) { for (i=yl ;i く =y2 ; i + + ) { mvwprintw(win, i, xl , tate ) #endif MS-C MS-C て、コンパイルする場合には , 以下の ようにコンパイルスイッチを設定するとと もに , MS ー C コンパイラのディレクトリ (MSC) 下の INCLUDEi•ィレクトリの中に , confi g. h として以下の内容を入れておく必要があ 応用 C 言語 109
djgcc 詳解講座・Ⅷ 0 刪 World VRAM など , 特定の物理アドレスを操作 するために , 物理アドレスと論理アドレス の対応を指定するオプションて、す。引数と して , 開始論理アドレス , 開始物理アドレ ス , サイズ ( バイト数 ) をコンマて、区切って 指定します。 IBM ー PC て、はこの機能はグラフィックス ドライバて、実現されていますが , 98 て、は代 わりにこのオプションを使って簡単に指定 んど使用されることのない ANSI トリグラフ を使用可能にする ( * 13 ) 。 -ansi' が指定された場合て、も , 予約語 asm inline および __typeof は引き続き使用することがて、きる。これら の予約語を ANSI C プログラム中て、使用した いとはもちろん思わないだろうが・ -ansi' に よってコンパイルされる場合にも読み込ま れるようなヘッダファイル中て、は便利てあ る。同様に既定義マクロ UNIX や vax などはを -ansi' があってもなくても有効て ある。 -ansi' オプションはそれだけて、は非 AN SI プログラムをむやみに拒否しない。非 AN SI プログラムを却下するには・一 ansi ' に加え て・ -pedantic' を指定する必要がある ( * 14 ) 。 -ansi' が指定されるとプリプロセッサマ クロ STRICT ANSI が定義される。へ ッダファイルはこのマクロを検出し , ANS I て、要求されていない関数宣言やマクロ定義 をやめることがて、きる。これらの名前をほ かの用途に使用しているプログラムと干渉 するのを防ぐためて、ある。 -traditional' 伝統的な C コンパイラをサポートしようと する。具体的には , 中にあっても大域的に扱われる。関数 ・すべての extern 宣言はたとえ関数定義の て、きるようになっています (Fig. 1 ー⑤ ) 。 387 DOS 工クステンダは , 数値演算プロセッ サ 80387 の有無を自動的に判定し , 80387 が ないのに浮動小数点演算命令を使うと実行 時工ラーを起こします。 しかし , 特別な理由て、 , この自動判定を 無効にして , 明示的に 80387 の有無の指定を の暗黙的宣言も同様て、ある。 ・予約語 typeof, inline, signed, const お よび v 引 at ⅱ e は無効となる ( この場合て、も 代替の予約語ー typeof inline な どは有効て、ある ) 。 ・ポインタと整数の比較は常に許される。 ・整数型 unsigned short および unsigned char は unsigned int へ変換される。 ・範囲外の浮動小数点定数はエラーとな らない ・文字列「定数」は定数て、なくてかまわな い。文字列定数は書き込み可能なメモ リ領域に置かれる。また , 同一の文字 列て、も別々の領域を割り当てる。 ・ register 宣言された自動変数はすべて IO ngjmp て、保存される。通常 GNUC は A NSIC に従う。つまり vo t ⅱ e 宣言され ていない自動変数は破壊されることが ある。 ・プリプロセッサにおいて , コメントは 空白に変換されずにまったく取り除か れる。この機能によって伝統的な字句 連結が可能となる。 ・プリプロセッサにおいて , マクロ引数 はマクロ定義の文字列定数の中て、も解 釈されるにの場合引数は引用符なして、 文字列化される ) 。プリプロセッサて、は 文字列定数は改行文字て、常に終了する。 -traditional' が指定されると既定義マ 行う場合は , 環境変数 387 に 80387 がつい ている場合は YES , ついてない場合は NO を 設定します。 たとえば , 80387 がついているマシンて、 , set 387 = NO を実行してからプログラムを走 らせると , 浮動小数点命令が実行されたと きにエラーを出してくれるのて、 , あるプロ グラムが 80387 を使うかどうか ( 80387 がなく ても動くかどうか ) をチェックするのに利用 最適化を行う。最適化コンパイルは大き な関数に対して時間およびメモリをより多 く必要とする。 ー 0 ' を指定しない場合 , コンパイラの目 的はコンパイル時のコストを減らし , デバ ッグ時に予期された結果を出すことて、ある。 実行文は独立て、ある : 文と文との間にプレ ークポイントを設定してプログラムを止め , 変数値を変更したりプログラムカウンタを 同じ関数中の別の文へ変更したりしても , 結果はソースプログラムから予期したもの になる。 ー 0 ' なしの場合 ,register と宣言された変 数のみがレジスタに割り付けられる。コン パイル結果のコードは・一 0 ' なしの PCC ( * 15 ) によるコードよりやや悪いものとなる。 クロ STDC は定義されない。しかし GNUC は定義される ( なぜなら GN U C の拡張機能は・ -traditional' によら ず有効だからて、ある ) 。を -traditional' に よって動作の変わるヘッダファイルを 書く必要がある場合には , これらふた つのマクロの両方を調べて , 以下の四 つの場合を区別しなければならない GNU C, 伝統的 GNU C, 非 GNU A NSIC コンパイラ , そして非 GNU 伝統 的 C コンパイラ。 djgcc 詳解講座・ Hello GCC World 77