define - みる会図書館


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

1. 月刊 C MAGAZINE 1991年5月号

#if 0 #define BIG ENDIAN / * Define if ー am a Big Endian machine * / #endif プリプロセッサの仕様変更 #define foo(xyz) puts("HellO xyz") もパラメータの置換が行われていました。 にほとんどの処理系て、文字列定数の内部て、 されていたわけて、はありませんが , 実質的 以前のプリプロセッサて、は , 明確に規定 トクンべースのバラメータ置換 の変更はかなり致命的て、す。 よいだけのことなのて、すが , 最初のふたっ directive) に関しては , それを用いなければ プリプロセッサ制御命令 (preprocessing このうち , defined( 識別子 ) と # elif 以下の ・ #pragma の追加 ・ #error の追加 ・ # e ⅱ f の追加 ・ defined ( 識別子 ) の追加 ータの新設 ・トークンの連結 ( # # ) と文字列化 ( # ) オペレ ・トークンべースのパラメータ置換 も重要な仕様変更は次の点て、す。 ANSI C のプリプロセッサにおけるもっと えます。 に混乱の度合いをましただけのようにも田 束していくのて、しようが , 現時点て、はさら 規定したため , 今後はこの規格に向けて集 ANSI C の規格はプリプロセッサを明確に ラムは移植が困難になるのが通例て、した。 プロセッサの機能に依存したソースプログ とも方言の激しい部分て、した。過度にプリ プリプロセッサは , 長い間 C におけるもっ f00 ( WO d ) ; bar( ) 54 C MAGAZINE 1991 5 puts( ” HellO World"); この機能はかなり広く利用されていまし た。しかし ANSI C のプリプロセッサて、はこ のような置換は行わないことになりました。 同様の機能を ANSI て、実現するには次のよう #define foo(xyz) puts("He110 " #xyz) , こて、 , ANSI による新機能てある文字列 化演算子 # を用いています。 # xyz と指定する と , マクロの実引数て、ある W 。 rld の両側を " て、囲んだ " Wrold " に展開するのて、す。そのた め , f00 ( World ) ; の最初の展開形は次のよう になります。 puts( ” He110 " "World"); このソースをコンパイルすると , ANSI の 新しい規約て、ある「相続く文字列定数はひと つに連結する」が適用されて , 最終的に puts("HeIIo WroId"); と同等の結果が得ら れます。もうひとつ , 重要な機能にトーク ンの連結があります。以前の C て、は , これは かなりの裏技として , 知る人ぞ知るという テクニックて、した #define CONCAT(), Y) X/* */Y 上記のようにマクロを定義した場合 , コ ンパイラによっては , マクロの展開後に X と Y の置換結果が連結されてひとつのトークン となるものがありました。しかし , ANSI て、 はコメントは空白文字と同じとみなされる ためこのような連結は動作しません。あく まて、も , X の置換結果と Y の置換結果のふた つが , 間にひとつ ( 以上 ) の空白文字を置い て書かれていると解釈されてしまいます。 なお , 古い C コンパイラて、のトークンの連結 このソースのプリプロセス結果は次のよ うになります。これは , 文字列定数の内部 にマクロのパラメータと同じ部分文字列 ( xyz ) が存在するからて、す。なお , 文字列定数だ けて、はなく , 文字定数の内側も置換対象に なっていました。 bar( ) にはもうひとつの手法が知られています。 #define CONCAT I(X) X #define CONCAT(), Y) CONCAT 1 (X)Y しかし , この手法て、あっても必すしもす べての処理系て、期待どおりに動作するとは かぎりません。また , このような手法を用 いても , ANSI て、はやはりトークンは連結さ れません。ところが , プログラミングにお いては , トークンの連結がて、きると非常に 便利な場面がしばしばあります。そこて、 , ANSI て、はトークンを連結するための演算子 を導入しました。 #define CONCAT(), Y) X # # Y 問題はこれらの機能の差を移植可能にす ることて、す。例によって , environ. h の内部 て、プリプロセッサシンポル ANSI を見てマク ロの定義を変えます。 #ifdef ANSI # define MAKE STRING(S) #S # define CONCAT(), Y) X # # Y #else # define MAKE STRING(S) "S" # define CONCAT(), Y) X/* */Y #endif ただ , MAKE STRING というマクロを 呼んて、も , 古い C て、は文字列定数を連結する ことがて、きないため , 上て、示したようなプ ログラミングはて、きません。この制限を承 知の上て、コーディングするしかありません。 たとえば , 最初の例をこれらのマクロを使 って移植可能にコーディングすると次のよ うになります。 #include ” envi 「 on. h ” #include <stdio. h> #define foo(xyz) \ printf( ” HeIIO 0/os*n", \ MAKE STRlNG(xyz)) bar( ) f00 ( WO 目 d ) ;

2. 月刊 C MAGAZINE 1991年5月号

初の文法て、あるとするならば , ごく大ざっ ばにいって C はそれ以来文法の修正を 2 度受 けています (TabIe 1 参照 ) 。 C て、記述したソフトウェアの移植性を論じ る場合 , どの範囲の文法に関して移植性を 保証するのかがまず問題になります。たと えば , 明らかに関数プロトタイプを用いて コーディングされているソースの場合には , ANSI C コンパイラのレベルて、しか移植可能 にはなりませんにの点に関しては後述しま す ) 。なお , 本稿て、は K & R と V7 を総称して 「古い C 」と呼びます。 幸いにも , 新しい文法が導入されたとき には , 可能なかぎり古い文法のスーパセッ トとなるように考慮されてきました。 K&R の古い文法に沿って記述されたソースて、も , 多くの場合は ANSI C コンパイラて、そのま ま , あるいはごく些細な修正て、コンパイル て、きます。したがって , 次のことがいえま す。 最大限の移植性を確保したければ , 古 い文法の範囲内でソースを記述せよ ただし現実問題として , 今や純粋の K & R 文法のコンパイラはほとんど残っておらず , 大部分のコンパイラは V7 文法か ANSI C だ と思ってもよいて、しよう。そこて、 , 筆者は 非 ANSI コンパラへの移植性も確保せざるを えない場合には , V7 の文法の範囲て、記述す ーー・移植上の障害点 K&R と ANSI C の相違 ることて妥協しています。 れば , ほとんどの場合 V7 に対しても移植は ら , K&R と ANSI C に対して移植可能て、あ 外を除けば両者の中間に位置していますか ておきましよう。 V7 コンパイラは少数の例 能なソースを書くための基本方針を解説し その前に , K&R と ANSI C の間て、移植可 にウェイトをおいて解説します。 違に関して , 移植上の障害になるような点 K&R と ANSI C, および V7 の文法間の相 基本的な方針は , なるべく K & R の文法の 枠内て、記述するというものて、す。しかし , どうしても両者て、矛盾してしまう部分もあ ります。そのような場合の対策は条件コン パイルて、す。すなわち , プリプロセッサを 利用して , ある種のプリプロセッサシンポ ルが定義されているかどうかて、コンパイル すべきソースの一部を変化させようという ものて、す。多くの場合 , マクロの定義を変 更したり , 付加的なヘッダを # include したり して対処します。 ANSI C 準拠のコンパイラの場合 , STDC というプリプロセッサシンポルは 1 と #define されることになっています。した がって , 次のような条件コンパイルを行え ばよいように思えます。非 ANSI コンパイラ の場合 , そもそも STDC は定義されて いないはずだからて、す。ここて、 , # if defined ( STDC ) と記述するのは避けるべきて、 す。なぜならば , defined( 識別子 ) というの は ANSI C て、導入された機能だからて、す。 #ifdef STDC / * ANSI コンノヾイラの場合 * / #endif たいていの場合これて、うまくいきますが , ほとんど ANSI C の規格に準拠していなが ら , いくつかの理由から STDC が 1 にな っていない処理系も存在します。そのよう な処理系を用いる場合には , コンパイル時 たとえばコンパイラに対するコマンド ラインオプションて、 STDC を定義する か , あるいは STDC に頼るのをやめ て , 自前のプリプロセッサシンポルを用い ます。本稿の後半を読んて、いただくとおわ かりになると思いますが , ANSI C かどうか 以外にも , 環境に特有のパラメータをいく つか指定しなければならないケースがほと んどて、す。そのような場合にも , 自前のプ リプロセッサシンポルを用いて条件コンパ #else / * 非 ANSI コンパイラの場合 * / イルをすることて、 ( たとえばマクロの定義 内容を変えて ) 対処します。 自前のプリプロセッサシンポルを用いな ければならない場合は , たとえば環境に依 存する部分を制御するためのヘッダをひと つ用意して , environ. h とて、も名前をつけ , その中に環境依存の部分をすべて閉じ込め るようにします。そうすれば , 移植する場 合にはこのヘッダだけを修正すればよいは ずて、す OANSI C かどうかによって動作を変 化させたい場合は , このヘッダ中て、次のよ うに記述すればよいて、しよう。 #define ANSI / * undef if not ANSI * / #ifdef ANSI / * ANSI コンノヾイラの場合 * / #else / * 非 ANSI コンパイラの場合 * / #endif このようにして , もし ANSI C を用いる場 合には #define ANSI をいかしておき , 非 ANSI コンパイラの場合にはコメントにして 殺してしまうという手段が考えられます。 あるいは , ソースの中に #define ANSI を入 れるのはやめて , コンパイル時のオプショ ンて、定義してもよいて、しよう。 それぞれの条件コンパイルの場合に , 具 体的にどのような作業を行うべきかは以下 て、述べます。 バラメータタイプリストと 関数プロトタイプ ANSI C が導入した新しい機能のうち , コ ーディングスタイル上もコンパイラ処理上 もいちばん重要て、あり影響力があるのは , パラメータタイプリストと関数プロトタイ プて、あることは論を待ちません。 ラメータタイプリストとは , 次の関数 f00 の 引数定義に用いられる ( long a, long b ) のカ 可能となります。 46 C MAGAZINE 1991 5

3. 月刊 C MAGAZINE 1991年5月号

応甬 C 言語 C の道具箱 List 4 イル用なのて、 , コンパイルスイッチをそれ ぞれ以下のように設定し , コンパイル後オ プジェクトファイルを読者のライプラリに 追加し , 最後にメインプログラムにリンク する必要がある。 Turbo C Turbo C てコンパイルする場合には , コ ンパイルスイッチを以下のように設定する とともに , Turb0 C コンパイラのディレク トリ下の INCLUDEi-•ィレクトリの中に config. h として以下の内容のファイルを入れ ておく必要がある。また統合環境て、コンパ イルするのて、はなく , TCC を使用する。 tcc -v -K -J -w -ml -c ( デバッガを使用 /char を unsigned char にする / 漢字を使用する / 警告あ り / ラージメモリモード / リンクは行 わずコンバイルのみ行う ) く Turbo C 用 config. h> #define TURBOC #ifndef MSDOS #define MSDOS #endif #ifndef PROTOTYPE #define PROTOTYPE 8 : 9 : # i fdef PROTOTY PE 10 : #include "cboxprot. h" 11 : #endif / * スクロール範囲のチェック * / 13 : sclwidck(scrol lwid) 14 : unsigned *scrollwid; if(*scrollwid 〉 SCROL し一い MIT) 17 : *scrollwid = SCRO しし一い MIT ・ if(*scrollwid ← 1){ *scrollwid 20 : return ( ー 1 ) : 22 : retu rn ( の : 23 : } watchgll.c List 5 変 を 性 属 の 行 0 十し + レ の 内 0 ウ ン 一イ・ 8 0 0 0 0 ・ , 0 ・ 1 十レ事しーー , 十レ -1 ワっ 0 -4 -06 々ー 8901 っ 0 っ 0 -4 【 00b 々ー 89010 乙っ 0 【 000 々ー 8q001 っ 0 れ -060 ー 8q00 っ 0 っ 0 っ 0 ワ 0 0 乙 00 っ 0 っ 0 っ 0 っ 0 っ 0 っ 0 っ 0 -4 -1 , 1 111 11 111 より 0 っ 0 っ 0 っ 0 #endif MS-C MS-C< コンパイルする場合には , 従来と 同様にコンパイルスイッチを設定するとと もに , MS ー C コンパイラのディレクトリ (MSC) く MS-C 用 config. h> 下の INCLUDE ディレクトリの中に , config. #define MSC h として以下の内容のファイルを入れておく #ifndef MSDOS 必要がある。 #define MSDOS CL /AL /Od /W3 /Zi /J /c 関数 . c #endif ( ラージメモリモード /C0deView を使 #ifndef PROTOTYPE 用 / 警告レベルは 3/char を unsigned char にする / リンクは行わずコンバイ #define PROTOTYPE ルのみ行う ) 104 C MAGAZINE 1991 5 [ 参考文献 ] WilIiam James Hunt "The C Toolbox", Addison-Wesley Publishing Company, 1989 #endif

4. 月刊 C MAGAZINE 1991年5月号

Fig. 5 wh ⅱ e 文の構文図 while 文 Fig. 4 wh ⅱ e 文の流れ wh ile ( * * ) 文 * * はゼロ 文 式 while * * を評価 * * は非ゼロ 文 やすくなったとはいえ , まだ改良の余地が つ出現するのて , 求める体重の範囲を変更 マクロの利用 あります。それは 150.0 と 190.0 という数値 しようとしたら , とてもめんどうな作業を List 3 のプログラムは List 2 よりはわかりて、す。これらの値はプログラム中に 2 か所ず 強いられます。そこて、マクロと呼ばれるも のを使用します。マクロを使って書き直し 標準体重を求めるプログラム ( wh ⅱ e 文とマクロを使用 ) たのが List 4 て、す。 3 ~ 5 行目て、マクロ名定義 (macro name definition) を行っています。たとえば 3 行目 は , #define HAZIME 150 ℃ と記述されています。ワープロやエデイタ て、の置換操作はご存知だと思いますが , # define による指定は , その置換と同じような ものて、す。 List 4 のプログラムは , リスト中 の HAZIME が単純に 150.0 に置き換えられて からコンパイルされます。 このような単純な置き換えが行われるも のをオプジェクト形式マクロ (object-like macro ) と呼びます ( もう少し複雑な置換が行 われるマクロもあります ) 。また HAZIME の ような置換される名前をマクロ名 (macro name) と呼びます。 置換が行われるの <,List 3 と List 4 のプ ログラムは本質的には同じて、す。しかしマ クロの利用により , プログラムの変更 ( この 場合は体重を求める範囲や間隔 ) が容易にな ります。また 150.0 のような数値を直接プロ グラム中に埋め込んだ場合よりも , プログ ラムの可読性が向上します。 単に # define てマクロを定義するだけて、な リストに示すようにコメントを記述す ることにより , プログラムの読み易さが一 段と向上します。 マクロ名は通常の変数名と区別しやすい LiSt 1 : # i nc 1 ude く stdio. h> 2 : 3 : #define HAZIME 150. 0 190. 0 4 : #define OWARI 10.0 5 : #define STEP 6 : 7 : int main(void) / * 身長と体重 * / 9 : float height, weight; 10 : printf ( " % f から % f までの標準体重を求めます \ n " , HAZIME, OWARI); 12 : height = HAZIME; while (height ← OWARI) { 13 : (height ー 10 の * 0.9 : 14 : weight printf("%f %fYn" height, weight) : 15 : height = height 十 STEP; 17 : return(0) : 19 : } 限限 * 下上幅 ののみ 重重ざ 体体き / * 標準体重を表示 * / 標準体重を繰り返し求めるプログラム List く stdio. h> 1 : # i nc 1 ude 2 : 3 : int main(void) 5 : int cont : 6 : float height, weight; 7 : do { 8 : pr i ntf ( " 身長を入力して下さい・ 9 : scanf("%f ・・ &height) : (height ー 100 ) * 0.9 : weight printf(" 標準体重 = %fYn" weight) : 12 : 13 : printf ( " もう一度計算しますか [ 0 ・・いいえ / 1 ・・はい ] 14 : scanf("%d ” &cont) : } while (cont) : 17 : return ( の : 19 : } / * 続けるかどうか * / / * 身長と体重 * / 124 C MAGAZINE 1991 5

5. 月刊 C MAGAZINE 1991年5月号

C プログラマのための LISt 1 19 : 20 : 22 : 23 : 24 : 26 : 28 : 38 : 39 : 40 : 42 : 43 : 44 : 45 : 49 : 50 : 52 : 53 : 54 : 55 : 59 : 62 : 69 : 77 : 78 : 21 : し SI C ー 86 Ver 3. 20 tcc -w diskfree. c 17 : Turbo C 2. 0 Small Model 29 : #include く dos. h> #include く ctype. h> 27 : #include く stdiO. h> lcc diskfree. c -lmathlib 25 : * / 32 : #define diskfree-t 47 : { 75 : } / * ト・ライア番号無効 _TURBOC_ dos-getdiskfree avai l_clusters 34 : #define total_clusters 35 : #define bytes-per-sector 37 : #endif 33 : #define 31 : #define 30 : # ifdef getdfree dfree df_avail df_total df_bsec 36 : #define sectors-per-cluster df-sclus unsigned long tobyte(unsigned) : int main(int,char * * ) : struct diskfree-t d iskspace; int main( argc, argv ) int argc; 46 : char *argv[] : int drive = 0 : int fdrive_err; if ( argc > = 2 ) { 十十 argv; if ( isalpha( **argv ) ) / * ト・ライフ・番号 0 : カレントト・ライフ・ / * ト・ライフ・番号 toupper( **argv ) drive 56 : # ifdef _TURBOC_ dOS getdiskfree( drive, &d iskspace ) : fdr ive_err 60 : # e I se fdrive_err 63 : #endif diskspace. sectors-per_cluster = 0xffff; / * ト・ライフ・番号無効 dos-getdiskfree( drive, &diskspace ) : / * 空き領域に関する情報の取得 if ( fdrive-err ) { " ト・ライフ・名 %c: が無効です。 Yn", fprintf( stderr, drive return( 1 ) : pr i ntf ( " \ n % 1 田 u バイト : 全ディスク容量 " " \ n % 101u バイト : 使用可能ディスク容量 % 5. If %%Yn" tobyte( diskspace. avai 1 clusters tobyte ( d i skspace. tOta 1 cl usters ( float )tobyte( di skspace. total ( float )tobyte( diskspace. avail-clusters ) * 100.0 / return ( 0 unsigned long tobyte( cl ) unsigned cl; clusters / * クラスタ数 / * クラスタ→ハ・仆変換 formatcO (formatc ー h も同じ ) 簡単なヘルプメッセージを表示します 安全のためデフォルトのドライプの処 理を行っていません 初期化するときには必ず , ドライプ名 または ドライプ名十ポリュームラベル を指定する必要があります formatc のコンヾイル ディスク再初期化ユーティリティ formatc のコンパイルは Fig. 5 のように行ってくださ い。なお , アプソリュートディスクリード / ライトの INT 25h/26h(Table 1 , 2 ) を呼び 出す関数 absdisk は MS-C Ver. 5.1 と Turbo C Ver. 2.0 て、は int86x 関数て、記述可能て、す が ,LSIC-86 Ver. 3.20 て、はスタックに積ま れたフラグの処理が行われないため , int86x 関数て、記述て、きません。よって MASM 用の リストを absdisk. asm として , また , 本ユー ティリティをコンパイルする際に必要て、は ありませんが , MS-C Ver. 5.1 と T 町 CVer. 2.0 用の関数 absdisk を absdisk. msc として 付録ディスクに収録しました。参考にして いただければ幸いてす。 MS-C Ver. 3.0 てはⅲ t86x 関数の INT 25h / 26h に関する部分に誤りがあるようて す。ほかのコンパイラに移植される場合に は注意してください Turbo C Ver. 2.0 には absread, abswrite 関数が用意されていますが , リード / ライト 用のバッフアが near'* インタてあるため , スモールモデルては使用てきません (FAT 領 域は 64K バイトを超える可能性がありま おわりに 今回は MS ー DOS のディスク管理に関して 新 MS-DOS プログラミング入門 95

6. 月刊 C MAGAZINE 1991年5月号

extern long f00 (long, long); extern double bar (double, double); 一方 , ANSI が定義されていない場合には 引数は ( ) となり , OId style になります。類 似の手法として次のようなものがあります。 #ifdef ANSI # define DCLFUN(ret, name, arg) \ extern ret name arg #else # define DCLFUN(ret, name, arg) \ extern 「 et name( ) このマクロ DCLFUN(DeCLare FUNc tion) を用いて先の例を記述するとこうなり ます。 DCLFUN(Iong, f00 , (long, long)); DCLFUN(double, bar, (double, double)); ただし , これらの記述方法にはいくつか の制限があることに注意しましよう。 まず , これらのマクロはいずれも関数宣 言にしか使えません。関数定義本体は Old #endif 48 C MAGAZINE 1991 5 定義を Old style て、行うと警告が発せられる がプロトタイプて、なされている場合に関数 ` 、く一部のコンパイラて、すが , 関数宣言 手′ 3 : マクロによる関数定義 ばしば見受けられることて、す。 ったことて、はなく , C プログラミングて、はし く記述て、きないというのは , この場合に限 このように typedef を併用しなければうま DCLFUN(funcp, fn, (int n)); typedef int * (*funcp)(void),• その場合には , 次のようにします。 int * (*fn(int n))(void); なして、は不可能て、す。 宣言を DCLFUN を用いて行う場合 , typede ばなりません。たとえば , 次のような関数 そのようなときには typedef を併用しなけれ 雑なものは宣言て、きないことがあります。 マクロを使う場合 , 関数の戻り値の型が複 style て、行う必要があります。次に ,DCLFUN ものがあります。このような外れたコンパ イラ (OId style と ANSI スタイルて、コードの 生成方針を変更する優秀なコンパイラて、あ るという可能性もあります ) を相手にせざる をえないときには , 残念ながら関数プロト タイプの利用を放棄するか , あるいは関数 定義も ANSI スタイルて、行うくふうが必要て、 す。 そこて、 , 関数宣言だけて、はなく , 関数定 義もマクロて、行って , Old style と ANSI ス タイルを切り換えることを考えましよう。 ただし , 残念ながらこちらのほうはあまり エレガントな解とはいえない部分がありま す。というのは , 定義したい関数の引数の 個数が何個て、あるかに応じてそれぞれ別の マクロを使わざるをえないからて、す。これ は主として C のプリプロセッサにマクロの引 数を分解して複数のトークンを作り出す機 能が備わっていないことに起因します。と にかく , 引数の数が 0 個から 2 個まて、のマク ロ定義例を示しておきます。 #ifdef ANSI # define DEFUN0(ret, name) \ ret name(void) typel, argl) \ # define DEFUN1(ret, name, type2 arg2) ret name(typel a 「 gl, typel, a 「 gl, type2, a 「 (2) \ # defineDEFUN2(ret, name, 「 et name(typel a 「 gl) #else # define DEFUNO(ret, name) \ typel, argl) \ # define DEFUNI(ret, name, ret name( ) 「 et name(argl) typel argl : # define DEFUN2(ret, name, typel, argl, type2, arg2) \ #endif type2 arg2; typel argl ; ret name(argl, arg2) を確保しています。 テジを用いて K & R と ANSI C の間の移植性 タて、ある GhostScript のソースがこのストラ PostScript 互換のページ記述言語インタブリ ほうが現実的て、す。 GNU が配布している わないというような制限を置いて処理する 数宣言や関数定義て、はマクロ置換は一切行 法にある程度限定を加えます。たとえば関 ルを用いる場合には , 入力ソースの記述方 からて、す。そのため , もしこのようなツー 記述方法て、行われているケースがありうる て , 関数宣言や関数定義が通常とは異なる 入力ソースの中て、マクロ置換がなされてい たいへんて、す。手法 2 , 3 て、示したように も完璧に変換て、きるようにしようとすると ますが , 実際にはどのような入力ソースて、 ルタブログラム ) を作るのは簡単そうに思え うアプローチて、す。変換ツール ( 一種のフィ 仕様のコンパイラ間て、互換性を保とうとい ルを OId style に変換するように構成し , 両 て対応する方法て、す。たとえば ANSI スタイ ソースファイルを書き換えるツールを用い ースレベルて、の移植の可能性をあきらめ , 段て、あると考えるべきて、しよう。つまりソ これは , ちょっと邪道というか , 最終手 手法 4 : ソース書き換え方法 らにくふうが必要て、す。 と , このマクロて、は対応不能て、すから , さ け取る関数を定義する場合のことを考える なお , 後て、述べますが可変個の引数を受 return (a 十 b) > > 1 ; DEFUN2(Iong, f00 , long, a, long, b) ングすればよいのて、す。 数を定義する場合には次のようにコーディ このマクロを用いて , たとえば 2 引数の関

7. 月刊 C MAGAZINE 1991年5月号

ク、一 ら構成されていることと , MS-DOS て、はテ キストファイルの途中て、 ( 0x1a ) の文字コ ードが登場したならば , そこて、 EOF て、ある と見なす習慣になっているからて、す ( もっと もマイクロソフト社の見解て、は , テキスト ファイルの最後に 0x1a を書くのは「よくない 習慣て、ある」ということになっているようて、 すが ) 。 そもそも , これは両 OS の歴史的な事情が 影響している問題なのて、 , どちらがよいと もいいかねますが , C のプログラムて、は ( こ れまた歴史的に ) UNIX スタイルのテキスト 構造を仮定した処理を行うようになってい ます。そのため , MS ー DOS 上の C 処理系て、 は , 以前からテキストファイルを取り扱う ときは , ちょっとした処理を付加して UNIX スタイルのテキスト構造に見せかけるとい うことをしています。すなわち入力て、は CR / LF を LF だけに変換し , また 0x1a が出現した ら EOF 扱いとします。一方出力て、は LF の直 前に CR を追加してファイルに書き出すよう にしています。しかし , この処理はバイナ リファイルの場合に行えません。必然的に 両者を区別する必要が生じます。 ANSI のライプラリ規定て、は MS ー DOS を無 視するわけにはいかなかったのて、しようか , f 叩 en などの指令に、、 b クをつけるかどうかて、 テキストファイルとバイナリファイルの区 別をするという , これまて、の習慣を正式に 規格化しました。たとえばバイナリファイ ルのリードの場合には "rb" とし , テキスト ファイルのリードの場合にはヾ r 〃とするのて、 す。デフォルトがテキストて、あることに注 意しましよう。もちろん UN Ⅸ環境て、はどち らを指定しても動作は同じて、す。 ところが , これがまた新たな問題を生み 出してしまいます。現在 , 一部の UN Ⅸ系の 環境て , fopen に対してヾ rb 〃などというモー ド指定を行うと , ヾ b 〃というのは理解て、きな いといって実行時にエラーて、落ちるものが あります。これはまったく小さな親切大き なお世話というものてすが , 結局バイナリ モードの指定を行うかどうかも environ. h の 特集 中などて、決定する必要があります。 #ifdef TEXT FILE EXIST もうひとつの注意点は , 低レベル入出力 #endif # define READ TEXT # define READ BIN #else # define READ TEXT # define READ BIN 理系がありました。 の内部構造が , それぞれ異なってしまう処 ところが , この結果てきあがるファイル てあるように思います。 fw0, fwl, fw2 のどの関数を使っても同じ てあると思われます。これを出力するのは えてみましよう。 zot はトータルて、 32 バイト たとえば , 次のような構造体の配列を考 てす。 されない ( ことがある ) というのがその理由 書き出し時と異なる形態て、の読み込みが許 て、は , レコードというものが厳然と存在し , まく行えない場合があります。それらの OS しかし , 一部の OS て、はこのような操作がう その逆とかがなんの支障もなく行えます。 たファイルを 10 バイトずつ読み出すとか , う構成て、あり , たとえば 1 バイトずつ出力し ファイルは , 単純なバイトストリームとい コードの考え方て、す。 UNIX や MS-DOS の ファイルのアクセスて、問題になるのがレ レコードの概念 また微妙な間題を誘発します。 差異があることを認識していないと , これ を削除する処理系があります。このような と , CR の直後に LF がきている場合にだけ CR 行う場合に , 無条件に CR を削除する処理系 イルの処理て、 CR / LF を LF に変換する作業を さらに細かいことをいえば , テキストファ が処理系によって異なっていることて、す。 バイナリファイルの区別をするのかどうか (read/write) に対してもテキストファイルと C プロ フム。 参考文献 [ 1 ] char bar [N BAR] struct f00 { #define N ZOT 4 #define N BAR 8 #include <stdio. h> 円 90 PRENT ℃ E HALL , 'PORTABLE C ” Henry RABINOWITZ , Chaim SCHAAP , int fw0(FlLE- * fp) } zot CN ZOT] ・ return fwrite(zot, (N BAR * return fwrite(zot, int fw1(FlLE *fp) 「 etu 「 n fwrite(zot, (struct f00 ) , int fw2(FlLE *fp) sizeof(zot), sizeof N ZOT, fp); sizeof(char), N ZOT), fp),• また , 同様に fread などて、読み出す場合に も , 書き出し時とレコードサイズが異なっ ていると , 必ずしも要求したバイト数をす べて読まずにリターンしてしまうのてす。 これは ANSI 規格に違反しているのてすが , ANSI 以外の処理系てあれば文句はいえませ ん。現実にそのような処理系がある以上 , ファイルの入出力においてレコード形式を 一致させるように注意するしか防衛手段は ありません。 特集 C プログラム移植入門 65

8. 月刊 C MAGAZINE 1991年5月号

ク、一 ッコの内側の記述を呼びます。 / * ANSI style, pa 「 ameter type list つき long f0000ng a, long b) return (a 十 b) > > 1 ; 特集 C プロ ANSI 以前の C コンパイラて、は以下のよう に定義しなければなりません。現在て、はこ れを OId st ⅵ e と呼びます。 / * 0 style * / long fOO(), b) long a, b; return (a 十 b) > > 1 : があります。この宣言も ANSI C て、は次のよ 関数を呼び出す場合には宣言が必要な場合 また , 別の所 ( 別のファイル ) からこの f00 イラが引数の個数と型のチェックを行って くれるため , 工ラーを発したり , 必要に応 じて型変換をしてくれたりするからて、す。 さらに , ANSI C の文法規格書 fFuture language direction ( 言語の今後の方向 ) 』て、 は , OId style は「 obsolescent feature ( すた れかかっている機能 ) 」て、あるということに なっています。 obsolescent feature は将来 的に本当に廃止されてしまう可能性が濃厚 て、す。このため , 一方的に Old style だけを 用いてコーディングしていると , そのソー スは将来コンパイル不能になってしまう恐 れがあるのて、す。 そこて、 , K&R の Old style と ANSI スタイ ルとを両立させるためにさまざまな手法が 使われています。 プ宣言を行うのか , Old style 宣言を行うの て、あるかどうかに応じて , 関数プロトタイ 件コンパイルを行い , コンパイラが ANSI C include します。そしてこのヘッダの中て、条 めたヘッダを作っておき , これをつねに # は , 外部リンケージをもつ関数の宣言を集 ッダを #include するようにします。もしく ANSI C て、あると判断される場合にはこのヘ 臥しておきます。使っているコンパイラが ロトタイプだけを集めたヘッダ ( funcs. h ) を用 style を利用して記述します。そして関数プ ソースプログラムの本体はすべからく Old 手法 1 : ヘッダ方法 #define ANSI #define ENV 旧 ON H #ifndef ENV 旧 ON H environ. h [ 例 ] かを決定します。 #endif / * ENV 旧 ON #define FUNCS H #ifndef FUNCS H ー funcs. h フム。 'environ. h ” #include #endif / * FUNCS H * / この例て、 , environ. h の中て、 ENVIRON H #include ” funcs. h' #include ” environ. h ” prog ・ C #endif / * ANSI * / double bar( ) ; long f00 ( ) ; #else double bar(double, ・ double); long foo(long, long); #ifdef ANSI / * または * / extern long foo(long a, long / * 0 style の関数宣言 * extern long f00 ( ) ; こて , extern は省略可能て、す。 うに書くことがて、きます。 / * 関数プロトタイプ * / extern long foo(long, long),• また関 数プロトタイプにおいては , 仮引数名 ( こ , て、は a , b ) も省略可能て、 , 書いても書かなく ても同じてす。 このように , 明らかに両者の文法は異な ります。しかしながら , 現在 ANSI C コンパ イラは OId style もサポートするように要請 されています。したがって , 「古い文法の範 囲内て、ソースを記述する」というルールに従 えば , Old st ⅵ e を用いるのが正解といえま す。 しかし新しい機能の誘惑は強く , また関 数プロトタイプなどは現実的な利益も大き いためぜひ使いたいところて、す。というの も , 関数プロトタイプを用いると , コンパ というプリプロセッサシンポルを用いて条 件コンパイルしていたり , funcs. h の中て FUNCS H というプリプロセッサシンポル を用いて同様に条件コンパイルしています が , これらは同じインクルードファイルを 2 度以上 #include した場合に変数の二重定義 て、エラーが発生したり , 定義ずみのマクロ を再度定義してしまうムダが発生するのを 防止するための手法て、す。 手法 2 : マクロによる関数宣 たとえば , 次のようにマクロ ARG ( x ) の定 義を行います。 #ifdef ANSI # define ARG(x) x #else # define ARG(x) ( ) #endif このような定義がなされているという前 提の下に , 関数の宣言を次のように行いま す。 extern long f00 ARG((long, long)),• extern double bar ARG((double, \ double)); こうまれば , プリプロセッサシンポル ANSI が定義されている環境ては , これは関数プ ロトタイプに展開され , 次のようになりま す。 特集 C プログラム移植入門 47

9. 月刊 C MAGAZINE 1991年5月号

ク、一 ことが可能て、す。従来はいつ変更されるか わからなかったのて、 , そのようなことはて、 きませんて、した。したがって , 今後作られ るプログラムは , 文字列定数を変更しない ように注意しなくてはなりません。 また ,ANSI C の規格て、は , ソースの異な る箇所に登場する文字列定数て、も , まった く同じ値のものはお互いにまとめてしまっ て , 実体としてはひとつだけ確保すること にしてもよいことになりました。このこと は , 文字列の先頭文字のアドレスが同じか どうかて、確認て、きます。従来は基本的にす べて異なる実体が割り当てられていました (UNIX の非 ANSI の C コンパイラて、 , 同一値 の文字列定数をひとつにまとめるオプショ ンを備えているものはあります ) 。このよう な性質に依存するようなコーディングは避 けなくてはいけません。 ANSI C て、は , main のありうる宣言形式 として次のふたつを規定しています。 int main(void) { / * int main(int argc, cha 「 *argv 冂 ) main の型と戻り値 特集 C プロ フム。 注目すべきは , いずれも main の戻り値の 型が int になっていることて、す。これを根拠 に , main の型が int 以外になっていると有無 をいわさず警告を発する ANSI 準拠のコンパ イラがあります。よく , main からは取りた てて値を戻していないからと , 戻り値の型 を void と宣言しているソースがありますが , これは ( void 型のない K & R への移植上の問題 を別にしても ) あまり望ましくありません。 int と書くのが嫌てあれば , むしろ何も書か ないほうがよいと思います。 また , ANSI C て、は main 内部の return 文 て、プログラムの終了ステータスを実行環境 (OS) へと戻せるように規定されましたが , これも古い処理系て、は必ずしも保証されて いないため , exit 関数を用いて終了ステータ スを戻したほうが無難て、す。ただし , main の戻り値の型を int と宣言したか , あるいは とくに型を指定せず暗黙のうちに int と宣言 されている場合には , 何も値を戻さないと 警告を出すコンパイラが存在します。した がって , 結論としては main の内部 ( おそらく は最後 ) に return 0 ; などを置き , それとは別 にプログラムの終了ステータスを戻すため に exit 関数を呼び出すのがいちばん無難な方 法て、しよう。 ユーザプログラムとライプラリ間 での外部シンボル名のクラッシュ K&R て、定義されていなかった関数が , ANSI はたくさん定義されました。 たとえ ば , div という関数がライプラリ関数として 定義されました。これは stdlib. h の中て、宣言 されています。したがって , stdlib. h をイン クルードしているソースの中て、 d ⅳという外 部変数や関数を宣言しようとすると名前が 衝突し , 工ラーになります。 ほかにも , abs という関数は K & R て、は定 義されていませんて、した。 ANSI て、はこれも stdlib. h の中て、関数として宣言されていま す。古いプログラムて、は , これを次のよう にマクロて定義している場合があります。 #define abs(x) ((x) > = 0 ? \ このように abs をマクロとして定義してい るソースを ANSI 環境て、コンパイルする場合 には , その定義が stdlib. h の #include よりも 先に行われていると , 後から出てきた stdlig. h 内部の abs の関数宣言もマクロ置換してし まうために , コンパイルエラーになってし まいます。また , 一部の処理系て、は abs のマ クロ定義がある種の標準ヘッダの中て、行わ れているものがありました。 マクロの場合 , 実引数が int ても , long て も , float ても , double< も支障なく動作し ます。しかし , 関数て、はそうはいきません。 ANSI ライプラリの abs は int を受け取り int を 返します。そのため , abs がマクロて、あるこ とを前提に , たとえば double を渡すと double が返されることを期待しているようなソー スを ANSI 環境へ移植すると , int になって結 果が返されるために , 思うように動作しな い場合があります。 コメントのネスト K&R て、も ANSI C て、も , コメントのネス ト ( コメントの内側にコメントを入れること ) はて、きません。ほとんどのコンパイラはこ れに従ってネストを許さないようになって います。しかし , かって一部のコンパイラ て、は「拡張」を施してコメントのネストが可 能になっていました。これは次のようなソ ースをコンパイルしようとすると問題を発 生してしまいます。 / * #define BIG ENDIAN /*Define if ー am a Big Endian machine*/ この行は , 現在コメントになっています。 しかし , 場合によっては ( 移植したマシンが Big Endian て、あれば・・・ これについては後 述します ) コメントをはずして # define を生か そうというものて、す。その場合 , 行の先頭 の / * を削るだけて十分て、す。この行にもと もとコメントがついているのて , その終わ りの * / がコメントを終了させてくれるから てす。ただし , コメントのネストがて、きな い場合に限った話てすが・・・ もしコメントのネストが可能てあれば , このような書き方はてきません。本来のコ メント部分 ( / * Define if I am ・ が , ネストしたコメントてあるとみなされ てしまい , コメントの終了記号がひとつ不 足していることになるからてす。したがっ て , そのようなコンパイラのことを考える と上にあげたようなソースの殺し方は好ま しくないことになります。めんどうてすが , #if 0 ・ ・ #endif を用いて次のようにコー ディングすべきてしよう。 集 C プログラム移植入門 53 1 ー 16

10. 月刊 C MAGAZINE 1991年5月号

ク、一 数が省略される可能性があることを示して いるのてすが , 問題は 2 番目の引数て、ある 。 flag て、す。本来の使い方としては , は fcntl. h などを #include することて定義され る O RDONLY とか O WRONLY といった プリプロセッサシンポルを渡すことになっ ています。しかし , UN Ⅸ環境ては例外なく O RDONLY は 0, O WRONLY は 1 になっ ているのて , UN Ⅸに慣れている ( 事実は「ず ばら」なだけの ) プログラマは , ついつい # include する手間を嫌って , それを定数とし て書いてしまいがちて、す。ところが , TCC は O RDONLY が 1, O WRONLY が 2 にな っています。こういう原因て発生するバグ はきわめて始末が悪いものとなります。繰 特集 C プロ 移植入門 フム。 り返しますがマジックナンバをソースの中 にちりばめてはいけません。 文法拡張の問題、 : ! にそれが激しいのが MS ー C6 と TCC て、しよ 準からはずれた部分をもっています。とく にしても , 各コンパイラは大なり小なり標 て、詳しく述べることにしますが , それを別 このメモリモデルに関連する問題は次の節 などは , そもそもは拡張機能だったのて、す。 感じなくなってしまっている near, far, huge 然となってしまったため , なんの違和感も ています。いまや i80X86 の C て、は , あって当 ほとんどのコンパイラは文法が拡張され Fig. 5 ライプラリの実装方法 ①関数として実装する ( LAT ) ③ポインタ変数へのポインタを取り , unsigned へのポインタへとキャストして # define FP-OFF(fp) ((unsigned) (fp)) # define FP-SEG(fp) ((unsigned) ((unsigned long) (fp)>>16 ) ) ②セグメントをシフトで取り出す ( TCC , LSI) exte 「 n unsigned FP—OFF(cha 「—far* ) : extern unsigned FP—SEG(char—far* ) ; 十 1 のところを取り出す ( MS ー C5 , MS-C6, QC) #define FP-SEG(fp) ( * ((unsigned*)&(fp) 十 1)) #define FP-OFF(fp) ( * ((unsigned*)&(fp))) Fig. 6 MS-C5, MS ー C6 と TCC との正規化の方針の違い MS-C5, MS ー C6 の正規化方針 セグメント : X000 の形にするⅨ = 0 ~ F ) オフセット : 物理アドレスから ( セグメント値 * 16 ) を引いた残り ただし , ポインタの加減算に際してセグメントの下位 12 ビットを積極的に 000 とするような操作はしない。 TCC の正規化方針 オフセット : 000X の形にする ( X = 0 ~ F ) セグメント : 物理アドレスの上位 16 ピット部分をそのまま使う う。 特集 c プログラム移植入門 69 能であれば , 使用する関数を限定する 定義をしたうえで用いる。それカ坏可 変数に関しては , 可能なかぎりマクロ ②インラインアセンプラやレジスタ疑似 て使用する 特定の関数に限ってローカル変数とし わざるをえないときは場所を厳選し , は , できるだけ避ける。どうしても使 ① based や es * などの特殊なポインタ 考えてみましよう。 ります。そこて , ひとつのガイドラインを 場合によっては使わざるをえないこともあ ば使わないほうが望ましいのてすが , 時と 能は非常に大きな障害となります。てきれ しかし移植を考えると , これらの拡張機 となります。 を考えれば非常にコンパクトて高速なもの も , ほかの手段て、レジスタに値を渡すこと の処理系が不要てすし , 生成されるコード アセンプラと異なり , 独立したアセンプラ 変数の機能は非常に便利て、す。インライン う拡張機能があります。このレジスタ疑似 ウェアレジスタに直接アクセスてきるとい いわゆるレジスタ疑似変数て℃ PU のハード すが , それとは別に AX などという形の , にはインラインアセンプラも備わっていま く使いこなせないと思われます。また TCC 成の実態をある程度把握していないとうま 言します。ただし , コンパイラのコード生 フセットを有するポインタ」て、あることを宣 ドウェアのセグメントレジスタに対するオ * , cs * , ss * があります。これは「ハー based の簡易版といった形式の es*, ds 能になります。一方 , TCC には MS-C の とがて、きるため , 効率的なコード生成が可 だけを操作するようなコードを生成するこ 任意のセグメント値を指定し , オフセット という意味をもつものて、す。これによって , ト内部て、のオフセット値を有するポインタ」 加されました。 based は「指定したセグメン segment, segname というキーワードが追 MS ー C6 て、は based およびそれに関連する