もうーっ , getFunc を呼び出している部分 にも注意しよう。これまた List 4 における table を用いた関数呼び出しと同様 , あまり 見かけない形式になっている。 printf( ” %4d", getFunc(k)(i, グするほうが , ループの最内側て switch な 十分な環境ては , List 7 のようにコーディン い。したがって , コンパイラの最適化が不 しようとするといちばん内側て行うしかな む。ところが switch て、呼び出し関数を決定 内側 <table[k] という参照を行わなくてもす List 7 のようにすれば , ループのいちばん ておく ) 。 行効率を高めることがて、きる ( main だけ示し ングは List 7 に示すように変更することて実 て、あるが , List 4 の main の内側のコーディ ような違いはある。実は先ほど示した List 4 行上のペナルティは少ない。ただし , 次の て、 , このように書き換えてもプログラム実 イルしたコードの実行効率はかなりよいの り , それて、なくても通常 switch 文をコンパ テープル参照にコンパイルされるものもあ コンパイラによっては switch は実質的に 6 のように表現することもて、きる。 ば , List 5 て、示した getFunc という関数は List とて、も代用することが可能て、ある。たとえ ば switch 文などを用いて条件判断を行うこ 呼び出し関数を「計算」する処理は , たとえ 念のために補足しておくと , このように のて、ある。 存在するという奇妙な表現が可能になった って , 引数をくくるカッコが二つ連続して のて、ある。これまた ANSI の新しい文法によ して本来行いたかった関数呼び出しを行う が返されるのて、 , それに二つの引数 i , j を渡 び出しを行う。その結果「関数へのポインタ」 すなわち , まず getFunc(k) という関数呼 printf("%4d' (*getFunc(k))(i, j)); R の文法て、は次のように書く必要があった。 すて、に想像されているように , これも K& どの手法を用いて呼び出し関数を決定する よりも望ましいといえる。ただし , コンパ イラの最適化の程度によっては ,List 4 のま まて、も自動的に List 7 と同じコードが生成さ れる場合もあり得ることは注意してほしい ( とにかく最近は非常に頭のよいコンパイラ があるのて、 , コーディングによって効率を 上げるということはだんだん困難な作業に なりつつある ) 。 外から機能を与えるための 関数へのポインタ 関数へのポインタには , もうーっ重要な 用途がある。それは , あらかじめ機能が決 定て、きない部分を関数へのポインタを用い て間接的に関数を呼び出すようにコーディ ングしておき , 実際の使用に当たって具体 的な機能を果たす関数へのポインタを供給 するというものて、ある。この用途のほうが , ほかの手段による代替が難しいという意味 て、 , 関数へのポインタのより本質的な用途 てあると考えることもて、きるだろう。これ を「外 ( そと ) から機能を与えるための関数へ のポインタ」と呼ぶことにしよう。 more して処理を行う必要がある。ところが , そ ーザ側が用意したデータの大小関係を判断 とがより端的になる。いずれの関数も , ユ ートを行うための qsort 関数などて、はこのこ 2 分探索を行うための bsearch 関数や , ソ 相談て、ある。 はなく , また一意に定めることもて、きない うな処理をしたいと望むかなどわかるはず してもいないプログラムの終了時にどのよ い。これは当然て、ある。ューザがまだ作成 行わせればよいかを決定することはて、きな ラリを作成した時点て、はどのような動作を 時点 , つまりコンパイラベンダーがライプ かに , atexit という関数それ自身を作成した を指定 ( 登録 ) するためのものて、ある。明ら ム終了時に自動的に呼び出してほしい関数 な atexit を例にとろう。この関数はプログラ たとえば , この 4 種類の中て、いちばん簡単 すべて外から機能を与えるためのものて、あ の関数が要求している関数へのポインタは ると Fig. 2 の 4 種類が見つかる。実はこれら ンタを引数として受け取るものを探してみ ANSI ライプラリの中から , 関数へのポイ Fig. 2 関数へのポインタを引数として受け取るもの ( 1 ) ( 2 ) ( 3 ) ( 4 ) #include VOid #include int #include VOid #include VOid く signal.h> ( * signal(int sig, void ( * func)(int)))(int) ・ く stdlib. h> atexit(void ( * func)(void)) ・ く stdlib. h> * bsearch(const VOid * key, const VOid * base, Size t nmemb, Size t size, int ( * compar)(const void * , const void * ) ) く stdlib. h> qsort(void * base, size t nmemb, size t size, int ( * compar)(const void * , const void * ) ) , ANSI C : more 111
LiSt Program Maintenance UtilitY(pmk. c) 147 : / * 2 : 3 : 4 : 6 : 7 : 9 : 16 : 20 : 21 : 23 : 25 : 28 : 33 : 36 : 37 : 40 : 44 : 45 : 48 : 49 : 50 : 52 : 53 : 54 : 55 : 56 : 58 : 59 : 60 : 61 : 62 : } 63 : 65 : 68 : 69 : 70 : 72 : 73 : } 74 : 1 : / * pmk. c 5 : #ifdef #pragma #pragma 8 : #endif 10 : #include く stdio. h> 11 : #include く stdlib. h> 12 : #include く string. h> 13 : #include く malloc. h> 14 : #include 15 : #include く dos. h> Program Ma i ntenance Ut i ⅱ ty. Copyright 1991 by Yasunori Fuzi i. C 叩 yleft 1991 for Project pragma C. Ⅲ STORY REVISED "yfz, 08 ー ap に 91 , base for Computer lnnovatioin, C86P し US. " REVISED "yfz, 12 ー ap に 91 , touch to Microsoft 6.0 and Quick 2.0. " く process. h> 76 : 78 : 79 : 80 : 82 : 83 : 84 : 85 : 86 : 87 : 88 : 89 : 90 : 92 : 93 : 94 : 95 : 96 : 97 : 98 : 99 : 100 : 101 : 102 : 103 : 104 List 1 75 : / * オプションフラグの作成 * / int makeflags( int argc, char *argv[] ) int argn; char CC, *CP, *sp : memset( P し AGS, 0 , MAXF し AGS ) : for( argn = 1 : argn く argc; argn 十十 ) 17 : #def i ne MAXF し AGS 128 18 : static char F し AGS[ MAXFLAGS ] : 19 : static char *F し AGSMEMBER="hv" ・ int VERBOSE ニ 0 : 22 : / * PMK. C 内関数プロトタイプ int main( int, char * * ) : 24 : void usage( void ) : int makeflags( int, char * * ) : 26 : VOid error( Char * ) : 27 : void error2( char * , char * ) : int loadmakefile( char * ) : 34 : Char * convert_macro( Char * , Char int expand_macro( char * , char * ) : 32 : VOid set_command( char * ) : 31 : VOid set macro( char * , Char * ) : 30 : void set_target( char * , char * ) : 29 : void set_target-or-macro( char * ) : i nt argn : 47 : char makef ⅱ ename [ 80 ] : int main( int argc, char *argv[] 43 : char * findcommandfile( char * ) : 42 : char * serchfile( char も char * ) : 41 : char * serchpath( char * ) : int execute( char * ) : 39 : void update( int ) : 38 : unsigned long getfiletod( char * int istargetfile( char * ) : int maketargetfile( int ) : 35 : void makefiles( void ) : / * オプションフラグステイタス * / / * 有効オプションフラグ文字 / * V ERBOSE MODE ON/OFF F し AG cp ニ argv[ argn ] : e ー se F し AGS [ cc ] while( cc if( *cp ー if( ! *sp ) return -1 : for( sp ニ F し AGSMEMBER; *sp ! = cc; sp + + ) return 0 : return argn; 105 : 107 : 108 : 109 : 110 : 112 : 113 : 114 : 115 : 116 : 118 : 119 : 121 : 123 : 124 : 125 : exit( ー 1 ) : fprintf( stderr. "YnY7ERROR: %sYn" VO i d error ( char * message ) 106 : / * ェラーメッセージを表示して EX は message ) : VOid error2( char * messagel, char *message2 ) fprintf( stderr, " YnY7ERROR : %s %sYn" , message 1 , exit( ー 1 ) : message2 ) : 120 : #def i ne MAXTARGET 122 : struct _makelist Char *name : 64 fputs( "YnProgram MaKe uti lity version 1. 0Yn" stderr fputs( "CopyIeft 1991 by ProjectPragmaC. Yn", stderr ) : return 0 : makef ⅱ es ( ) : printf( ” \ n ” ) : loadmakefile( makefilename ) : i f ( argn ) strcpy ( makef ⅱ ename. if( F し AGS[ 'v' ] ) VERBOSE if( ( argn く 0 ) Ⅱ ( FLAGS[ 'h' ] ) ) usage(): argn = makeflags( argc, argv ) : strcpy( makefilename, "MAKEFILE" ) : 126 : 127 : 131 : 132 : 133 : 134 : 136 : 137 : 138 : 139 : 140 : 141 : 143 : 145 : 146 : char *line; Char *name; struct macrolist 135 : #define MAXMACRO struct make ⅱ st *next : 128 : struct makelist TARGET[ MAXTARGET ] : 129 : struct _makelist COMMAND[ MAXTARGET ] : 130 : struct _makelist *COMMANDLINK; i nt TARGETCOUNT : 0 : argv [ argn ] ) : 128 64 : / * 解説 VOid usage( VOid ) fputs( "Usage: PMK [-options] [makefile]Yn", stderr ) : fputs ( " fputs ( " fputs( "Yn" ex i t ( 1 ) : C MAGAZINE fputs( "Yn -optionsYnYn" stderr ) : V verbose on.. h he ゆ .. .. 実行情報を表示 \ n " この説明を表示 Yn ". stderr ) : stderr ) : stderr ) : 1991 6 142 : struct _macrolist MACRO[ MAXMACRO ] : 150 : FILE *fp; int loadmakefile( char *makefilename ) MAKEFI し E をロードする * / 144 : int MACROCOUNT ニ 0 : 149 : 148 : 54
C プログラマのための List 4 処します。その後よく調べてみると , 名前 のないビットフィールドて、生成コードがお かしくなるということを発見しました。 のバグは sbits4 のようにダミーの名前をつけ れば簡単に解決します。 List 5 は LSI C ー 86 Ver. 3.20 試食版のバ グ ( もしかしたら仕様かもしれませんが ) て す。 signed char を int に代入するときに上 位バイトを 0 にするため , cdat & Oxff ; idat としても cdat が符号拡張されてしまいます。 LSI C ー 86 て、はデフォルトて、 char が unsigned になっていますが , char のデフォルトを signed にしているときには注意が必要て、す。ちな みに漢字を処理するときは char を unsigned にしておかないと , いろいろとトラブルが 多くなります。たとえば , iskanji( cdat & Oxff ) などて、は cdat が漢字コードだと符号拡張さ れるため , 正常に動作しません ( jctype のテ ープルからはみ出してしまう ) 。 このように生成コードのバグはコーディ ングを変えてアセンプリリストを眺めなが らトライ & 工ラーを繰り返すしかないて、しょ う (List 3 ~ 5 に対応するアセンプリリストは 付録ディスクに収録しました ) 。複雑な式を いくつかの簡単な式に分けて調べることも よくあります。 コンノヾイラのノヾグへの対処はメーカーに よって違います。 LSI Cー86 や Turb0 C/ C 十十て、は , わりとこまめにバージョンアッ 節約て、きます ( 時間も貴重な資源て、す ) 。 れらの方針はメーカー次第て、ユーザが口を プしているようて、すが , MS ー C は一度製品版 グの修正に関してどのような方針をとって はさめるところて、はないて、しよう。 を出すとそのバージョンて、はバグは修正さ もかまいませんが , バグ情報はて、きるだけ しかし , 一ユーザとしては , コンパイラ れず , ューザから寄せられたバグは次期ハ 公開してほしいものて、す。現在 , バグ情報 メーカーにすべてのバグ情報のすみやかな ージョンのデバッグ項目に加えるといった を公開しているのは LSI C くらいて、すが , ほ 対策をとっているようて、す。 MS-C はそれだ 公開を要望します ( コンパイラにかぎりませ かのコンパイラも追従してほしいと思いま んが ) 。バグの原因を突き止めるにはそれな けほかのコンパイラと比較して版の完成度 す。また雑誌て、コンパイラの使い方を説明 が高く , 出荷された製品版も安定している りの時間がかかります。公開されたバグ情 するよりは , バグ情報を公開してもらうほ ようて、す。バグを頻繁に修正するかどうか 報があればそのバグを避けてコーディング うがユーザにとっては有益て、す。 は製品価格に直接ひびいてきますから , すればよいわけて、すから , デバッグ時間が uns igned 22 : 23 : } sbits2; 24 : 25 : void func2( void ) 27 : sbits2. s0. bit0 28 : sbits2. sl . bit8 29 : } 30 : 31 : #define 田 T0 32 : #define BIT8 33 : 34 : unsigned sbit3; 35 : 36 : void func3( VOid ) 37 : { 38 : sb i t3 ト田 T0 : sb i t3 ト田 T8 : 39 : 40 : } 42 : struct { unsigned bitO 43 : 44 : unsigned dmyl 45 : unsigned bit8 46 : unsigned dmy2 47 : } sbits4; 48 : 49 : void func4( void ) 50 : { 51 : sbits4. bit0 52 : sbits4. bit8 53 : } 0X0001 0X0100 1 0 ー 1 よ 0 ー 符号拡張の不具合 ( LSI C ー 86 Ver. 3.20 試食版 ) LlSt int idat; 2 : signed char cdat; 3 : 4 : void funcl( void ) idat ー cdat & 0xff; 8 : 9 : void func2( void ) 10 : { idat 12 : } / * cdat が符号拡張される / * cdat が 0 拡張される ( unsigned char )cdat; 80 C MAGAZINE 1991 6
新 MS ー DOS プロクラミンク入門い C プログラマのための コラム コンヾイラのバクベの対処法 今回から本連載のプログラムに対応する 最適化のバグ ライプラリのバグは , ①等価のライプラリを自作してコンバイ 処理系に MS ー C Ver. 6.0 と Turbo C 十十 純然たる生成コードのバグ Ver. 1.0 を加えました。そこて、コンパイラ となるて、しよう。まず , 最初にすべきこと ラのライプラリよりも先にリンクする のバグ対処法を簡単に説明しましよう。 は , どこにバグがあるのかを突き止めるこ か , コンバイラのライプラリと差し換 コンパイラに限らずソフトウェアにとっ とて、す。当然 , 真っ先に疑うのは自分のプ てバグは避けることはて、きません。したが ログラムて、す。バグの原因を突き止めるに ②バグのあるライプラリを使用しないよ って , バグとうまくつき合うことも必要て、 は数秒 , 数分から数日とかなりの開きがあ うにプログラムを変更する す。コンパイラのバグは大別すると , ります ( 今回は誌面の都合上 , バグの突き止 などの対策が考えられます。 ライプラリのバグ め方は省略します ) 。 ①はライプラリのソースが手元にあれば 簡単て、すが , なければ仕様を合わせて自作 パイブッ″のバグ ( MS ー C Ver. 6.00A ) しなくてはいけません。雑誌の特集て、ライ プラリのソースを掲載したものがよくあり ますから , そのような記事を参考にすると よいてしよう。 ②はたとえば , fseek 関数にバグがあれば seek 関数を使用してプログラムを書き直し てみるといった方法て、す。 最適化のバグはどの最適化を行うとバグ が出るかを突き止めて , コンパイルスイツ チて、その最適化を抑制するのがもっとも簡 単な方法てす。 生成コードのバグはもっとも厄介なバグ て、す。これはコーディングを変えて試行錯 誤するしかないて、しよう。 List 3 の funcl は MS-C Ver. 6.00A のバ グて、 , unsigned< ーを使うと : を忘れ てとんてもないコードを生成してしまうと いうものてす。これは func2 のように を十 に変更するか , func3 のように unsigned を unsigned char に変えることにより対処てき ます。 List 4 の funcl は Turbo C 十十 Ver. 1.01 のビットフィールドに関するバグてす。 8 ビットのビットフィールドは正常のようて す。したがって , func2 のように 8 ビットのビ ットフィールドに変更するか , func3 のよう にビットフィールドを使用しないことて対 LlSt 1 : unsigned Char hour; 2 : unsigned far *pw 3 : 4 : void funcl( void ) ( hour % 10 ) ー 6 : 8 : 9 : void func2( void ) ( hour % 10 13 : 14 : unsigned char far *PC 15 : 16 : void func3( void ) pc[ 0 ] ニ 18 : pc[ 1 ] ニ 19 : 20 : } ( unsigned far * ) 0Xa0000000 し : / * 生成コート・が異常 *PW / * 正常 * / *PW = ( unsigned char far * ) 0Xa0000000 し : ( uns i gned char ) ( ( hour % 10 ) + ・ 0 ・ ) : / * 正常 * / ピットフィールドのバグ ( Turb0 C 十十 Ver. 1.01 ) LlSt 1 : struct { unsigned bitO 2 : 3 : uns igned unsigned bit8 4 : 5 : unsigned 6 : } sbitsl; 7 : 8 : void funcl( void ) 10 : sb i ts 1. b i t0 sb i ts 1. b i t8 13 : 14 : struct { struct { unsigned b i t0 17 : unsigned } s 0 : struct { 20 : b i t8 uns igned 、 1 ー 1 よ 0 ー 新 MS-DOS プログラミング入門 79
2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 12 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 21 : 22 : 23 : 24 : 26 : 28 : 29 : 30 : 32 : 33 : 34 : 35 : 36 : 38 : 39 : 40 : 42 : 43 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 14 : 17 : List 3 TASKER. HPP 25 : } : 44 : } : List 2 task (schedu I er& s) : / / タスクは常にスケジューラに結びついている int initialized() { return (int)task-frame; } / / タスクは初期化されているか ? public: unsigned long frame_size; / / アクチベーションレコードのサイズ void* frame_bottom; / / アクチベーションレコードの終端 void* frame_top; / / スタック上のアクチベーションレコードの開始位置 void* task_frame; / / スタック上のアクチベーションレコードをストアする場所 scheduler& owner; / / このタスクを起動・制御しているオーナー jmp_buf task_environment; / / ローカル定数を作る = 0X20 } : enum { stacksize class task { c lass scheduler; / / クラス名を宣言 #include く setjmp. h> #define _TASKER_HPP #ifndef _TASKER_HPP_ 1 : / / TASKER. HPP / / タスクの最初の初期化 ( コンストラクタではない ) / / in itia ⅱ ze ( ) からはリターンしないのだから : virtual -task() { delete task-frame; } VOid initialize(); virtual VOid task_main() / / 各タスクの "ma i n" vo id suspend ( ) : / / このタスクを休眠させる vo i d resume ( ) : / / 目覚めて最前の sus pend ( ) コールの直後から処理を続行する 27 : class scheduler { / / 保持するタスクの最大数 const sz; / / タスクの配列 ( 一般的にはリンクトリストが使われる ) SZ (size) scheduler() { delete tsk; } tsk ニ new task* [sz] : 37 : index(-l) { = 10 ) ・ scheduler(int size public: jmp-buf scheduler environment; 〃保留時にタスクが longjmp してくるところ : int index; int count; task** tsk; , count(0) , VOid add-task(task* t) : void run-tasks() : friend class task; / / 新たなタスクをスケジューラに加える TASKER ℃ PP 1 : / / TASKER. CPP 2 : #include ” tasker. hpp ” #include く stdio. h> #include く stdlib. h> #include く string. h> #include く conio. h 〉 13 : / / ( ini t ia ⅱ ze ( ) からはリターンしないのだからコンストラクタではだめ ) : 12 : / / タスクの最初の初期化 owner. add -task ( th i s) : / / 現在のタスクをスケジューラに加える : owner(s) , task-frame()U しい { task: :task(scheduler& s) frame_top (void*)_SP; / / task ー main ( ) 用にスタックスペースの余裕を unsigned char stackspace[stacksize] / / 配列を初期化してスタックポインタを強制的に下げる / / (resume ( ) 中の ) memcpy ( ) が自分のアクチベーションレコードを消さないように , VOid task::initialize() { て、眠りについて , 眼が覚めたら別の場所だ ったということと似ています。これこそ , マルチタスキングに必要なことなのて、す。 もちろん , このノンローカルな goto ( ) 関数 は , 上記以外にも様々なことに利用て、きま す。プログラム全体の中て、 , 複数の jmp buf を使ってもよいのて、す。プログラムがある 一定の良好なステートに達したときには , つねに同じ jmp buf を引数として setjmp() を コールするということもて、きます。そうす ると , 重大なエラーが生じたときなどに 最初に戻ることなく , 直前の良好なステー トへ戻ることがて、きます。たとえば , メニ ューシステムて、ユーザが入力工ラーをした 場合て、も , プログラムをアポートせずに回 復て、きます。 setjmp( )/longjmp( ) を使うときの , 注意 事項がいくっかあります。ます第一に , jmp buf は , それを引数とする longjmp ( ) がコー ルされる前に , 初期化しなければなりませ ん。初期化されていないと , どういう結果 になるて、しようか ( これは何世代にもわたる 哲学の難問て、す ) 。 longjmp( ) の引数にする jmp buf は , その時点よりも上のスタックを指して いなければなりません。 たとえば , List 1 の ( 悪い ) コード例を見て ください。これは , どういうことになるて、 しようか ? destination( ) をコールして jump buffer の内容をセットアップし , その jump buffer を引数とする longjmp( ) が , ス タックポインタも含むレジスタの値をリス トアして , destination() の内部へリターン するようにします。しかし , その次には destina tion() から main ( ) の内部へリターンするの て、 , ( 関数リターン時の暗黙の p 叩操作によ り ) スタックポインタ ( が指す番地 ) は上へ行 ってしまいます ! longjmp ( ) がコールされ ると , スタックがそれよりも低番地にある 環境へとジャンプします。しかし , スタッ クの , 現在のスタックポインタよりも下の C 十十で書く単純なマルチタスクカーネル 17
1 : #define EXTERN extern 2 : 3 : 6 : 7 : 8 : 9 : 10 : 13 : 15 : 17 : 18 : 20 : 21 : 22 : 23 : 26 : 28 : 29 : 30 : 32 : 33 : 34 : 35 : 36 : 38 : 39 : 40 : 42 : 43 : } List 3 # i nc 1 ude #include # i nc I ud e 5 : # i nc 1 ud e 4 : # i nc 1 ud e # i nc 1 ud e clockon( ) 関数 ( CM910401 ) List 2 27 : } 13 : } く stdio. h> く dos. h> く conio. h> く process. h> regs2. h ” ” pldwn. h ” 11 : #define mPd8259 0 12 : #define mPd8253 0X71 14 : #define EOI 0X20 #define TIMER 1 static short lntvCount; static long Count; void delayy(int); void (interrupt far *OrgVect) ( ) : VOid interrupt far timedate(void) : 24 : void clockon() -disable(); dos-setvect(0x08, timedate) ; outp ( mPd8253 + 6 , 0X36 ) : del ayy ( 2 ) : outp(mpd8253, IntvCount &0xFF) : deIayy(2) : outp(mpd8253, IntvCount > 〉 8 ) : outp ( mPd8259 + 2 , inp(mpd8259 + 2)CTIMER) : 0rgVect = _dos-getvect ( 0X08 ) : IntvCount = 24576 * 10 * 60 ・ enable() : 汪 . 参考文献 [ 3 ] 163 頁の StartTime 関数を参照 clockoff( ) 関数 ( CM910402 ) 1 : #define EXTERN delayy( ) 関数 ( CM910403 ) 1 : void delayy(n) i nt n : register unsigned int i : while(i 6 : 5 : 4 : 2 : List 4 List 5 getdver( ) 関数 ( CM910404 ) mlnor_num outregs. h. ah; maJor_num outregs. h. al; intdos( &inregs, &outregs ) : inregs. h. ah 0X30 ・ Char minors 20 char majors 20 int minor_num; int major_num; char *verstr; getdver ( verstr ) / * MS ー DOS のバージョンを得る 8 : #include "regs2. h" #include く string. h 〉 #include く dos. h> # i ncl ude く std I i b. h > #include く stdio. h> 1 : #define EXTERN extern 2 : 3 : 4 : 5 : 6 : 7 : 9 : 10 : 1 1 : 13 : 14 : 15 : 16 : 17 : 20 : 22 : 23 : 24 : 25 : 26 : itoa(major-num, majors, 10 ) : itoa(minor_num,minors, 10 ) : strcpy(verstr, majors) : strcat(verstr, strcat(verstr, minors) : return((int)outregs. h. al) 2 : 3 : 4 : 5 : 10 : 12 : 15 : 20 : 22 : { 23 : 24 : 25 : 26 : 28 : 29 : 30 : 31 : } 汪 11 : # i nc I ude # i nc I ude 8 : # i nc I ude 6 : # i nc 1 ud e # i nc 1 ude # i nc I ud e # i nc 1 ud e く stdio. h 〉 く dos. h 〉 く conio. h 〉 く process. h> ” dlregs. h" "regs2. h" ” pldwn. h ” List 6 inkey98( ) 関数 ( CM910405 ) / * 直接コンソール入力 inkey98( void ) 6 : #include ” regs2. h ” 4 : #include く dos. h 〉 3 : #include く stdi0. h 〉 1 : #define EXTERN extern 2 : 5 : 7 : 8 : 10 : 12 : inregs. h. ah = 0X00 int86( 0X18 , &inregs, &outregs ) ; return((int)outregs. h. al) 13 : #define mPd8259 0 14 : #define mPd8253 0X71 16 : #define EOI 0X20 17 : #define TIMER 1 19 : void (interrupt far *OrgVect) ( ) : 21 : void clockoff() -disable(); outp ( mPd8259 + 2 , inp(mpd8259 + 2) enable() : -dos-setvect(0x08, 0rgVect) : 参考文献 [ 3 ] 164 頁の StopTime 関数を参照 scandir2( ) 関数 ( CM910406 ) List 7 12 : # end i f #include "cboxprot. h" 10 : #ifdef PROTOTYPE 8 : #include "fkey. h ” 6 : #include く dos. h> #include く stdio. h> 3 : #define EXTERN extern 1 : # i nc I ud e ” config. h" 11 : 9 : 7 : 5 : 4 : 2 : 92 C MAGAZINE 1991 6
lnformation from Compiler Makers ムが対応て、きないためて、はないて、 W4 に設定し , 工ラーが出ない ように書き直すのもひとつの しようか。 仕様変更についてくわしくは , 方法て、す。 Reference 3 章ランタイムライプラ 2 最適化の抑制 リリファレンス , malloc( ) につい MS-CVer. 6.0 には , さまざ ての記述をお読みください まな最適化スイッチやプラグ マが用意されています。最適 Q List 2 の構造体をコンパイル 化を行ううえて、 , その機能を して , sizeof 演算子を使って調べた outtext 関数で , 文字を表示 理解せずに最高の / Ox のスイ ところサイズがレヾイト多いような した後に printf で表示すると表示位 ッチをつけている方もいます。 のですが。 置がすれてしまいます。なぜです また , デフォルトの状態て、も a は 2 バイト , b は 1 バイト , C は 4 最適化がかかりますのて、 , 複 か ? / ヾイトなので合計 7 / ヾイトになるは 雑なコードを解析する場合 , A outtext 関数は , 自分自身の 最適化を禁止する / Od を使用 プログラム中に , 現在のカーソル することをおすすめします。 ポジションを保持しています。文 3 作業メモリの確保 複雑なコードを解析する場合 , 字の出力を行うと , MS-DOS シス それだけメモリに負荷がかか テムのもっカーソル位置と , outtext 関数のもつポジション情報 ります。メモリをなるべく解 の両方を書き換えます。 放し , 残りメモリが十分にあ そのため , outtext 関数以外の関 る状態て、コンパイルするよう 数 (printf など ) て、 , カーソル位置が にしてください 変化した場合に outtext 関数は , 自分自身のもっカーソルポジショ ン情報をもとに表示するのて、 , MS Q まったく同じ環境で , ma 恥 c ( ) ー DOS のカーソル位置とずれてしま 系関数を使ったルーチンが , Quick います。 C Ver. 2.0 では動作するのですが , 回避方法としては , outtext 関 MS-C Ver. 6.0 ではうまく動作し 数を使用する場合は , システムの カレントボジションを変更するよ なくなりました。理由を教えてく うな関数との併用をさけることが 必要て、す。 すが , sizeof 関数の結果は 8 になっ List 1 のようなプログラムの場 てしまいます。 合 , flag 変数の宣言には , 必ず vola A tile キーワードを使用してくださ MS ー C は , データを高速にアク セスさせるために , 個々のデータ を必ず word 境界に配置します。そ のため奇数バイトのデータの後に 未使用の 1 バイト領域を取ってしま List 2 のように , 奇数バイトのデ ータを連続して配置させたいとき には , コンパイルの際に / Zp オプシ ョンをつけてください A Q ー List 1 flag func (void) while(!flag) int 0 11 っ 0 っ 0 -4 -. 0 7 ・ List 2 1 : #include 2 : 3 : struct test { 4 : int a; 5 : char b; 6 : I ong c : 7 : } abc : main(void) 9 : ViOd printf ("%d" 12 : } く stdio. h> A MS ー C Ver. 5.1 , QuickC Ver. 2.0 て、は , fmalloc 関数は必 要なメモリをヒープ領域に確保て、 List 1 のようなプログラムをコ きなければ , デフォルトデータセ ン / ヾイルしたところ , 永久ループ グメント内に割り当てようとしま となるようなコードが生成されて したが , ー MS-CVer. 6.0 て、はこの しまいます。変数ル g の値は , 外部 ようなとき , NULL を返すように 割り込みにより変化するので , 不 なりました。 都合です。 原因はこの仕様変更にプログラ sizeof(abc) ) ; Q lnformation from Compiler Makers 149
番地の中身は , ゴミて、あると覚悟しなけれ これは悲報て、す ( しかも , これによってエラーメッセージが出ること ばなりません・・・ いっているのは , この引数の値のことて、あ なのて、ある。上記て、「 1 。 ngjmp ( ) の返り値」と が , longjmp( ) になえる第二引数 ( int 型 ) の値 mp( ) の , 二度目以降の返り値とみなせる値 mp ( ) は void 関数なのて、 , 返り値はない。 setj もて、きます ( 訳注 : より厳密にいうと , longj を switch 文などのフロー制御文て、使うこと り値によって , 何かの情報を返し , その値 うな書き方て、もよいのて、す。 I 。 ngjmp ( ) の返 0 の値て、あることが必要て、すが , List 1 のよ のリターンとを区別するために , 通常は非 いません osetjmp() コールと longjmp() から longjmp ( ) の返り値は , 1 て、なくてもかま 情報をコヒ。ーした後て、のことて、す。 しますが , それは , 正しいと分かっている テムて、も , 実は下のスタックへとジャンプ さえあるて、しよう ) 。本稿のタスキングシス はなく , しばらくはまともに動作すること aking control t0 Task 42 : } 82 : } : List 3 19 : 20 : 22 : 23 : 24 : 25 : 26 : } 29 : 30 : 32 : 34 : 35 : 36 : 38 : 40 : 41 : 43 : 44 : 45 : 46 : 48 : 49 : 52 : 53 : 54 : 55 : 56 : 58 : 59 : 60 : 62 : 65 : 66 : 68 : 69 : 71 : 76 : 77 : 78 : 79 : 85 : 86 : / / 確保した後のフレームの開始点 task_main() : る ) 。 / / i n i t i a I i ze ( ) からリターンできない , なぜならその / / アクチベーションレコードが / / task_ma i n ( ) が s uspend ( ) によってスケジューラに / / I ongj mp した後の関数コールの間に / / スケジューラによって破壊されているからである 28 : void task::suspend() { (void*)_SP; frame bottom 〃ブッシュするとスタックポインタは値が減る . (int)frame-bottom; (int)frame-top frame_size if(!task_frame) task_frame ニ (void*)new 33 : char[frame-size] : memcpy(task_frame, frame-bottom, frame-size) ; if(!setjmp(task environment)) longjmp(owner. scheduler_environment, 1 ) : 39 : void task::resume() { memcpy(frame_bottom, task_frame, frame_size) : longjmp(task_environment, 1 ) : void scheduler: :add_task(task * t) { if(count > ニ (z) { puts("too many tasks") : ex i t (1) : 〃リンクトリストのコンテナでは不要 tsk[index]->initialize(); i f ( ! t sk [ i ndex] ー〉 i n i t i a ⅱ zed ( ) ) / / 初期化されているかチェック 〃 initialize() と resume() はともに , index (index + 1 ) % count; setjmp(scheduler environment) : / / スケジューラへの longjmp により void scheduler: :run-tasks ( ) { tskCcount 十十 ] / / ここにリターンする 〃次のタスクへ行く I 。 ngjmp ( ) によって飛び出す / / タスクへの最初のエントリ コンテキストスイッチ のトリック の 80X86 て、はメモリモデルにより ) 同じサイ 環境はつねに ( マシンにより , そして lntel いのて、 , それは自作しなければなりません。 ションレコードを保存する既製の関数はな は環境を保存してくれますが , アクチベー ステート から構成されています。 setjmp ( ) 分と , すべてのレジスタを含むプロセッサ 数のローカル変数などのあるスタックの部 ドーーー実引数 , リターンアドレス , その関 テートは , 関数のアクチベーションレコー と飛び込みは longjmp ( ) が実現します ) 。ス ストアしなければなりません ( この飛び出し それからジャンプ先の関数のステートをリ は , 今いる関数の全ステートを保存して , コンテキストスイッチを実行するために e I se / / 初期化ずみタスクの通常の処理 tsk [index]->resume() : 63 : / / デモ用に実際のタスクを作る 64 : / / (task-main() が class task 内で純粋仮想関数であることに注意せよ ) : task { / / べースクラスは private class task a / / タスクを識別するため char* id; 67 : public: task-a(scheduler& s, char* nm) private: void task-main() { / / タスクのスタートアップコードが通常はここにある / / この後 , 無限ループに入るにこではスタートアップは何も要らない ) for ( : : ) { / / 無限ループにして絶対にリターンしないようにする ! printf(" タスク %s ノフェース・ 1 ノナカタ・ヨー \ n " suspend ( ) : printf(" タスク %s ノフェース・ 2 ノナカタ・ヨー \ n " puts(" オワルカイ ?(y/n)") : id(nm) { } : task(s) , i d) : if(getch() suspend() : 84 : m a i n ( ) { scheduler s ( 3 ) ; task-a a(s, s. run-tasks ( ) : ' y ・ ) ex i t ( 1 ) : 18 C MAGAZINE 1991 6
more に対する実引数だとはわからない点て、ある。 関する変更はかなり重要なものて、あると考 そのため , NULL は「 void へのポインタ」と えるが , 不思議なことにあまり強調されて 同じ型て、あるとしてコンパイルされてしま おらず , 単に ANSI 標準て、は (*funcp)( ) を うのて、ある。 funcp( ) と書いてもよくなっただけて、あると たとえば , i8086 て、メモリモデルにミディ 田われているらしいのは残念なことて、ある。 ッじ、 アムモデルやコンノヾクトモデルを使ってい 関数へのポインタとキャスト るケースを考えてみよう。これらのモデル て、は「関数へのポインタ」と「オプジェクトへ void w 「 iteSt 「 ing(cha 「 *st 「 , のポインタ」のサイズが異なっている。 void C の機能の中て、も , キャストによって関数 void writeChar(char)) へのポインタは「オプジェクトへのポインタ」 へのポインタを作り出すことがて、きる機能 と同じサイズを有することが要求される。 は , とびきり野蛮なものだといえる。とい うのも , これによって任意のアドレスをコ そのため , 関数へのポインタを期待してい るところへ , void へのポインタとしてコン ールすることが可能になるからて、ある。た パイルされた実引数が渡されることになり , とえば次の式を考えてみよう。 両者のサイズは異なるためにきわめてまず い事態 ( おそらく , NULL て、はないと判断さ これによって 0 番地をコールすることがて、 こて、 , writeString は二つの引数を受け れることになり , writeChar の値を default きる。なお , i80X86 の場合には , コンノヾイラ 取る。ーっは「 char へのポインタ」型の str て、 WriteChar へとセットすることなく間接的に とメモリモデルに依存して , この記述て、は あり , もうーっは「 char の引数を一つ受け取 関数を呼び出そうとして暴走する ) を招いて 「現 CS のオフセット 0 」をコールするコードが り , 値を返さない関数へのポインタ」型の しまうのて、ある。 生成される場合があるのて、 , 0000 : 0000 をコ writeChar て、ある。そうして , もし 2 番目の これを防ぐためには , プロトタイプ宣言 ールしたい場合には , たとえば次のように 引数に対して NULL が渡された場合には をきちんと行っておくことが重要だが , 実 己述する必要がある。 defauItWriteChar という関数を呼び出すよ 引数の NULL を以下のようにキャストする うにしている。 習慣をつけるのも悪くない これはアセンプラ並の低レベルな機能て、 こて、 , writeString の内部の if (write writeString( ” HeIIO world ” あり , C て、はいとも簡単に暴走するプログラ NULL) という比較はまったく問 Char (void(*)(char))NULL); ムを書くことがて、きる理由て、もある ( もちろ 題がないコーディングて、ある。 NULL に関 このように , NULL のキャストには意味 ん , これはあまりほめられたことて、はな しては自動的に型変換されてすべてのポイ があるものとないものがあることを知って ンタ型と自由に比較て、きることが ANSI 標準 おいてほしい ところが , このように少なからず問題が ては保証されている。 ある「関数へのポインタ」へのキャストが , ところが , これを実際に呼び出す場合に [ 注 ] 場合によっては必要不可欠の場合もある。 はどうなるて、あろうか。別なソースファイ [ 1 ] LSI C ー 86 Ver. 3.20 ( 試食版 ) てはこの それは引数として「関数へのポインタ」を期 ルから , プロトタイプ宣言なして、 writeString 仮引数の記述はエラーになる。次バ 待している関数への実引数として , 何らか を呼び出そうとして次のようにコーディン ジョンて、はサポートされるとのことて、 の定数・・・・・・たとえば NULL を渡す場合て、あ グしたとする。 ある。 る。この場合て、も , あらかじめ関数プロト w 「 iteString("Hello world", NULL); [ 2 ] 「一次式」が「後置式」に変更されたこと タイプの宣言が行われていれば , 何も問題 プロトタイプ情報がえられていない場合 , も見逃せない変更なのてあるが , 誌面 はない これは誤った結果を引き起こすかもしれな の関係て、 , それに関してはここて、は触 い。間題は , そのような場合に , の呼び ところが , それが行われていないと危険 れない 出し側のコードをコンパイルする時点て、 2 番 なケースがある。たとえば , 次のような関 数を考えてみよう。 目の引数て、ある NULL が「関数へのポインタ」 #include <stdio. h> void defauItWriteChar(char c) putchar(c); if (writeChar writeChar while (*st 「 ) writeChar( * st 「十十 ) ; NULL) defaultWriteChar; 一三ロ ANSI C more 113
lnformation from Compiler Makers Fig. 2 class SampIeCIass{ private: int data; public: int get-data (void) {return data ; } void set-data(int value) {data = value : } void func ( const SampIeCIass& param) int param. get-data() a class SampIeCIass{ private: int data; public: int get-data(void)const {return data : } int set_data(int value) {data value ; } 内容 TabIe 1 クラスの一覧 クラス名 AbstractArray Array Assoc iation Bag BaseDate BaseTime Collection Container DoubleList Deque DictionaIy String Sta c k SortedArray Sortable Set Queue Object ListElement List HashTable E 作 0 「 DoubleListElement 基本クラス Collection AbstractArray Object HashTable Sortable Sortable Container Object ColIection Containe 「 Set なし Object CO llectio n Collection なし なし Container Bag Object AbstractArray Container Sortable ファイル名 strng. h stack. h SO a 「Ⅳ . h sortable. h set. h queue. h Object. h lstelem . h list. h hashtbl. h Object. h dlstelem. h dict. h deque. h dbllist. h contain. h collect. h ltime. h ldate. h bag. h assoc. h array. h abstarry. h ランダムアクセス可能な集合 要素の順序が定義されていない集合 Diction 引 y で取り扱うキーをもったオプジェクト HashTable と同じ ( 名前が違うだけ ) 年月日 時分秒 Containe 「に追加や削除の機能を追加したクラス オプジェクトの集合 双方向リンクをもつ線形リスト 両側から処理できる拡張キュー Association 型のオプジェクトの集合 双方向リンクをもつ線形リストの要素 new 演算子のエラー処理用 要素の順序をもたない集合 要素の順序をもつ集合 単方向リンクをもつ線形リストの要素 各クラスの基本機能を定義している抽象クラス キュー旧 FO ) 重複した要素をもたない場合 大小が比較できるクラスのもとになるクラス 要素の大小でソートされた集合 スタック旧 LO ) 長さつき文字列 lnformation from CompiIer Makers 151