define - みる会図書館


検索対象: 月刊 C MAGAZINE 1993年10月号
26件見つかりました。

1. 月刊 C MAGAZINE 1993年10月号

defined を用いれば簡潔に書ける 2 種類のマクロの例 List List / * オプジェクト風マクロの例 * / #define EOF #define Sign0n ” Hello, world ” #de f i ne D i g i t #define f00 bar #define NL putchar('\n') / * 関数風マクロの例 * / #define addl(n) ((n) + I) #define sqr(x) #define PRINT(val, fmt) printf(#val ' #if defined(ANSI) & & defined(UNIX) & & defined(SYSV) / * ANSI & & UNIX & & SYSV . #endi f BSD 系 UN Ⅸの C ソースでよく見かける記述 LiSt (val)) #fmt, / * これは ANSI 準拠ではない * / #ifdef LITTLE_ENDIAN / * LITTLE_ENDIAN が定義されている * / #else LITTLE_ENDIAN / * LITTLE_ENDIAN は定義されていない * / #endif LITTLE_ENDIAN マクロ定義されていれば , 正しい再定義 LiSt #define F00 / * comment * / ( x + Y ) / * another comment * / $define F00 ( / * comment * / x + Y / * more comment*/ ) #define BAR(x) ( x ) + 1 $define BAR( x ) ( / * comment * / ( x ) + 1 対応関係を明示したい場合はコメントで示す List / * こうすれば AN 準拠である * / #ifdef LITTLE_ENDIAN / * LITTLE_ENDIAN が定義されている * / #else / * LITTLE_ENDIAN * / / * LITTLE_ENDIAN は定義されていない * / #endi f / * LITTLE-ENDIAN * / 再定義として許されない例 LiSt #define F00 (x + Y) / * 空白を除去した * / #define F00 ( y + x ) / * マクロ本体のトークン順序が異なる * / ( ( y ) + 1 ) / * マクロ引数名が違う * / #define BAR(Y) ( (x) + 1 ) / * 空白が挿入されている * / #define BAR(x) ロ名を再び # define て、指定して別の定義を与 その定義が破棄される。指定した識別子が は識別子がマクロ名として定義されていな えることは実質的にはて、きない。たとえば , マクロ定義がされていなかった場合には , ければ , 引き続くソース行をコンパイルし 次の例を考えてみよう。まず , 単に何もしないだけて、エラーにはしない もし条件が成立しなければ , 引き ていく。 #define F00 ( x 十 y ) 続くソース行はコンパイルせずにスキップ 件コンノヾイル #define BAR (x) ( (x) 十 1 ) していく。条件コンパイルが終了するのは , とマクロ定義を行ったものとする。その後 , 条件コンパイルを開始するプリプロセッ #endif List 2 のような定義を行ったとしても , すべ サ指令は 3 種類ある。なお , ANSI 規格て、は というプリプロセッサ指令が出現したとき て正しい再定義て、ある。つまり , 実質的に 条件コンノヾイノレ (conditional compilation) て、ある。 同じ定義を繰り返しているとみなされる。 という言葉は使わず , conditional inclusion さらに , 次の二つの制御指令がある。 関数風マクロの場合 , マクロ引数リスト #elif 定数式 ( 条件取り込み ) という用語を用いている。 の中に余分な空白を挿入するのはかまわな 実際には , 必ずしもコンパイルの対象とな #el se い。逆にマクロ引数リストからは空白を除 らないソースを取り込むこともあるため , 最初の # elif は , else if の意味て、あり , 直前 厳密には ANSI 規格の用語のほうが正しいの にテストした条件コンパイルの条件が成立 去してもかまわない しかし , 定義そのものの場合には , 空白 かもしれない。しかし , 条件取り込みとい していて現在コンパイルが行われていたな がないところに挿入してはならず , 逆に空 う用語は一般的て、はないのて、 , ここて、は条 らば , 対応する # endif まて、スキップすること になり , 逆に直前の条件が成立せずにスキ 白があったのを削除してしまってはならな 件コンパイルと呼ぶことにする。 い 0List 3 に示すのはいずれも再定義として ップしている最中に出会った場合には , 新 #if 定数式 許されない例てある。 たに定数式を評価し , その結果が 0 て、なけれ #ifdef 識別子 もし , どうしてもあるマクロ名の定義を ば引き続くソース行をコンパイルしていく #ifndef 識別子 変更したければ , # undef を用いればよい ことになる。 0 て、あれば , 依然としてスキッ 最初の形式は , 定数式を評価し , その結 #undef 識別子 果が 0 て、なければ引き続くソース行をコンパ プを続ける。 #else のほうは , スキップ中に これによって , オペランドに指定した識 イルしていく。 2 番目の形式は識別子がマク この指令を見つけると , 引き続くソース行 別子がもしマクロ定義されていたとしたら , のコンパイルを行う。もし直前の条件が成 ロ名として定義されていれば , 3 番目の形式 138 C MAGAZINE 1993 10

2. 月刊 C MAGAZINE 1993年10月号

の行番号を意味する。すなわちコンパイラ は , あたかも現在のソースファイルの行番 号が指定された行番号てあるかのように解 釈し , その後の処理を続けるのて、ある。ま た「ファイル名」は , これが指定されている と , あたかも現在のソースファイル名がそ こて示されたファイルて、あるかのように解 釈して処理を継続する。 のだが・・ うというような用途や , 特定のシンポルを , 実は , この指令の与える効果が何て、ある その場合に問題になるのが , トランスレ なんらかの形て、定義していなければならな かを ANSI 規格は規定していない。考えられ ータがすべてのエラーを検出て、きればよい いのに , それを定義していないことを検出 ることは , その後のコンパイル作業におい のだが , そこて、は検出しきれなかったコー してエラーにするといった用途に用いる。 て , ソースファイル中て、エラーが検出され ディングエラーが存在し , それがコンパイ たとえば , コンパイラの種別の見分け方て、 ラによって検出されてしまった場合に , コ たときに , そこて、表示されるエラーメッセ あるが , 通常はプリプロセッサがコンパイ ージには , 工ラーの内容だけて、なくファイ ンノヾイラが表示するエラーメッセージのフ ラやバージョンを識別することがて、きるよ ァイル名 / 行番号と , もともとのソースプロ ル名と行番号を表示することになるだろう うな「定義済みの識別子」を用意しているた が , そこて、表示する値をこの指令て、制御て、 グラムとの対応が取れないということて、あ め , それを利用する ( List 7 ) 。 きるということて、ある。 る。つまりエラーが発生したことはわかる また , 識別子がマクロとして定義されて また , オプジェクト内部にデバッグ情報 が , トランスレータのユーザにとっては , いるかどうかというのは , たとえば , として同様のファイル名 / 行番号といった情 ソースファイルのどこが悪くてエラーにな #if !defined (OS) 報を埋め込み , 後にデバッガがそれを参照 っているのかが判然としないのて、ある ( トラ # error Define symbol OS ンスレータのユーザは , あくまて、処理系全 したり , あるいは実行時工ラーを起こした #elif OS 場合に , OS によってはトレースパックとい 体を C 十十とか PascaI のコンパイラて、あると / * UN Ⅸ * / う処理が行われ , どのソースのどの行に対 考えていることに注意 ) 。 応するオプジェクト部分て、エラーが発生し そこて、 , トランスレータの作者は , C にト たのかを表示してくれたりするのだが , そ ランスレートした後の出力ファイルの適当 のための情報として使われることになるだ なところに , しかるべき # line 指令を挿入す こうしておけば , トランス るようにする。 ただし , このプリプロセッサ指令は , 普 レート後の C ソースをコンパイルしたとき 通のプログラミングて、は使うことはないだ もし工ラーが発生したとしても , そこ #endif ろう。普通に C て、コーディングしているかぎ て、表示されるファイル名や行番号は , もと のように用いる。 りにおいては , そのようなことを気にしな もとのソースファイルのファイル名や行番 余談だが , この #error というのは , ないと くてもコンパイラがきちんとそれらの情報 号 ( ないしはそれに近い番号 ) となり , 容易 どうしようもないという種類のものて、はな にソースとの対応を取ることがて、きる。 を管理してくれるはずて、ある。 い。要するに , 特定の条件て、エラーを起こ て、はどんなときに使うのかというと , せばよいのて、あるから , むちゃくちゃなソ error 例としては , C 十十 to C とか , PascaI to C ースを書いておけばよいのて、ある。 # error が のようなトランスレータを作るとき ( 使うと ANSI 規格になって新設されたプリプロセ なかった時代によく使われたのは , ッサ指令て、ある。端的にいえば , 工ラーを きて、はない ) に利用することになる。それ #ifndef OS 発生させる指令て、ある。オペランドに指定 らのトランスレータが生成した C コードて、 You lose. Please define symbol OS. は , 当然ながら , プログラマが直接コーデ したプリプロセッシングトークンを , あた #endif イングした C 十十なり PascaI なりて記述した のようなフレーズて、あった かもエラーメッセージて、あるかのようにし ソースプログラムとは行番号が対応しなく て表示する。たとえば , よくあるケースと #pragma なる。たとえば , C 十十て、は 1 行の記述て、済 しては , 特定のべンダの特定のバージョン むことが , C て、は数行を費やさなければなら ( 以降 ) が備える機能に依存したコーディン プラグマはコンパイラに対する , コンパ ないということもあるからだ ( 改行を入れな イラ依存の指令を与えるために用いる。た グをしているときなど , それを見分けて , ければ , 何て、も 1 行て、済むというのも確かな 条件にあっていなければ工ラーにしてしま とえば , 最適化の程度とか , 同じ最適化て、 「定義済みの識別子」を利用する LiSt #if defined(C-MAGAZINE_C) & & VERSION > : 600 # se # error Use C-magazine C version 6.0 or later #endif 1 #elif OS 2 / * M 爪Ⅸ * / #elif OS 140 C MAGAZINE 1993 10

3. 月刊 C MAGAZINE 1993年10月号

らのハイライトの状態をコントロールしな ければならないのて、す。 今回のプログラムて、はイベントループの 中てイベントが activateEvt なら DrawGrow lcon を呼び出しています。このルーチンは 指定されたウインドウのハイライトの状態 を調べて適切なサイズボ、ツクスとスクロー ルバーの枠を描いてくれます。スクロール ーそのものはアプリケーションが activeF lag の値を見てハイライトの状態を制御しま す。今回はスクロールバーは使用していな いのて、 , 何もしていません。 アップテートイベント 前回も説明しましたが , アップデートイ べントはウインドウの再描画が必要なこと を示すためにウインドウマネージャが生成 します。これは , たとえばウインドウの重 なり合いの状態が変化し , それまて、は見え なかった部分が見えるようになったときに 生成されます。 Fig. 2 は , 下にあったウインドウ 0 をユー ザがクリックし , 手前に移動する様子を図 示したものて、す。このときのイベント処理 の流れは Fig. 3 のようになります。これらの イベントの中て、 mouseDown はユーザのアク ションによって発生するものて、すが , それ 以降のイベントは SelectWindow の結果とし てウインドウマネージャが生成します。 さて , Fig. 2 の (b) はアップデートの処理 中の様子を示していますが , この斜線の部 分がアップデート領域て、す。アップデート イベントに対するアプリケーションの処理 は BeginUpdate と EndUpdate て、囲んて、再描 画することは前回説明しましたが , このと きの描画は , 斜線のアップデート領域だけ にクリップされます。つまりアプリケーシ ョンがウインドウ全体を再描画しても , 実 際に表示されるのは斜線の部分だけという ことて、す。 アップデートイベントは新たにウインド ウを作成したときにも発生します。ウイン ドウが最前面に作成されたのならウインド ウ全体が見えているはすて、すから , このと 148 C MAGAZINE 1993 10 サンプルプログラム ( Samp 厄 4. c) List 4 1 : #include く Types. h> 2 : #include く OSUtils. h> 3 : # include く Menus. h> 4 . #include く Dialogs. h> 5 : #include く Controls. h> 6 : #include く Desk. h> 7 : # i ncl ude く TO 引 Ut ⅱ s. h > 8 : #include く Fonts. h> 9 : # include く 0SEvents. h> 10 : # include く Memory. h> 12 : #define MenuBarID 128 13 : 14 : #define AppleMenuID 128 15 : $define AboutItemID 1 16 : #define AboutAlertID 128 18 : #define FileMenuID 129 19 . #define 0penWindowItemID 1 20 : #define CloseItemID 2 21 : #def i ne Qu i t I teml D 4 22 : 23 : #define Edi tMenuID 130 24 : #define UndoItemID 1 25 : #define CutItemID 3 26 : #define C 叩 yItemID 4 27 : #define PasteItemID 5 28 : 29 : #define WindovID 128 30 : 31 : $define ErrorAlertID 129 32 : 33 : $define SBarWidth 15 34 : 35 : #define DefauItWindowT 叩 40 36 : #define DefaultWindowLeft 40 37 : #defi ne NewWi ndow0ffset 20 38 : 39 . #define WNE-TRAP 0X60 40 : #define UNIMPL-TRAP 0x9F 42 : int 石 ndO 響 T 叩 : DefaultWindowTop, 43 : int WindovLeft = DefaultWindovLeft; 44 : 45 : Boolean Done, 46 : MenuHandIe AppleMenuH, 47 : EventRecord TheEvent ; 49 : void error(Str255 nes) ParamText(mes, NULL, NULL, NULL) ; StopAIert(ErrorAlertID, NULL) : 52 : 53 : } 54 : 55 ・ void OpenMYWindow() 57 : WindowPtr 58 : static int n; 59 . Str255 str, 60 : i f ( ( 響 (WindowPtr)NevPtr(sizeof(WindowRecord))) error("%pout Of memory") : 62 : 63 : return; 64 : GetNewWindov(WindovID, 第 (void *)-1) : 65 : NumToString(n + + , str) : 66 : SetWTitle(), str); 67 : if (qd. screenBits. bounds. right ー WindovLeft く 20 Ⅱ 68 : 69 : qd. screenBi ts. bounds. bottom ー WindovTop く 20 ) { 70 : WindovLeft : DefaultWindovLeft; 新 nd0 訂叩 = DefaultWindovT 叩 ; MoveWindov(), WindovLeft, 石 ndo 訂叩 , false) : 73 : 74 : WindovLeft + ニ NewWindow0ffset; WindowT 叩 + ニ NewWindov0ffset; ShovWi ndO 響 ( の ; SetPort(w) : 77 : TextFont( の ; TextSize(48) : 82 : void CloseMyWindov(WindovPtr の 84 : = NU ししⅡ ((WindovPeek)v)->vindovKind ! ニ userKind) 85 : return; 86 : HideWi ndo 貨 ( の : 87 : ロ oseWi ndo 響 ( の : 88 : DisposePtr((void * ) の : 89 : } 90 : : NUL い {

4. 月刊 C MAGAZINE 1993年10月号

手法のものは , x と y の間に空白文字が置か れてしまう。すなわち , 明らかにダメな例 の 2 番目に等しくなる。プリプロセッサ指令 が解釈されるのはトランスレーションフェ ーズ 4 て、あるから , どちらも ANSI 規格て、は 使えない しかし , 上記のような要求 , すなわちマ クロの置換結果を文字列定数として利用す ること , および , ふたっ ( 以上 ) のマクロ引 数からひとつのトークンを合成することは , しばしば必要になる。このため , ANSI 規格 て、は二つの新しい発明を行った。「文字列化 演算子」の、、 # クと , 「トークン連結演算子」の 、、 # # クて、ある。 、、 # 〃は単項演算子て、あり , マクロ引数に前 置して , #define F00 (x) puts ()x " world") のように用いる。 、、 # 〃が前置されているマクロ引数は , 実引 数へと置換したときに , 自動的に両側にダ プルクオートが施される。実引数の中に ダブルクオート ( " ) や、、 \ 〃記号 ( 本来はバック スラッシュだが , JIS コードては円マーク ) が含まれていれば , それらは文字列の中に 記す場合にはエスケープしなければないが , その作業も自動的に行われる。この場合 , 置換直後には , FOO(helIo) → puts ('hello" " wo 日 d つ となるのだが , トランスレーションフェー ズ 6 において連続する文字列定数は連結され るから , これて、当初の目的を達することが て、きるのて、ある。ところて、 , 、、 # 〃演算子を適 用て、きるーも関数風マクロにおいてだけ て、あり , かっ , 、、 # クの後には ( 空白を置いて もよいが ) マクロ引数がきていなければなら , こて、 , ひとっ問題なのは , ANSI 規格て、 は「文字列定数の内部の置換」という問題は 、、 # 〃演算子と「隣接文字列の連結」というルー ルの導入て、クリアしたのだが , 「文字定数の 内部の置換」という問題は未解決だというこ 142 C MAGAZINE 1993 10 #define CTRL(c) もっとも , は , ANSI 規格ては書けないことになる。 とて、ある。先の CTRL マクロに相当するもの ( * (#c) & 0x1f) のようにいったん文字列化してその先頭文 字を取り出すようにすれば , かなり近い効 果を得られる。 この解決策は , 知っていると結構便利な ことが多いが , 実はひとっ致命的な問題が ある。それは , 、、 * クの適用には , オプジェ クトのアドレスを取るという作業が暗黙の うちに含まれ , その操作は「定数式」て、は許 されていないということてある。すなわち , このように定義した CTRL マクロは「定数式」 が要求されるところて、は使えないのて、ある。 ソ # クは二項演算子て、あり , #define cat (), y) x # # y のように用いる。、、 # # クてひとつのトークン なのて、 , 間に空白を入れてはならない これて、 , ふたつのマクロ引数 x と y をそれ ぞれ実引数に置換し , その後に両者を連結 してひとつのトークンに合成するのてある。 なお , 、、 # # 〃はマクロ引数以外のトークンに 適用してもかまわないし , 関数風マクロて、 もオプジェクト風マクロて、も用いることが て、きる。ただし , ソ # 〃をマクロの先頭ない しは末尾に置いてはならない / * どちらも正しい * / #define f00 L # # num #define bar(n) L # # n マクロ置換ては , もうひとつ重要なポイ ントがある。それはマクロ置換メカニズム て、ある。 こにも ANSI 規格が導入した新し いいくつかのルールがある。最初は , 「再帰 的な置換の禁止」て、ある。たとえば , #define char signed char のようなマクロ定義を考えてみよう。 このようなマクロがコーディングスタイ ル上望ましいかどうかとか , 現実問題とし て便利かという議論はさておき , このよう な定義をしても ANSI 規格てはいっこうに問 題は生じない。だが , ANSI 以前のプリプロ セッサて、は , おおいに問題があった定義て、 ある。昔のプリプロセッサの場合 , おそら くこのマクロは無限に展開を続けようとす るだろう。なぜならば , 置換後の文字列に また、、 char 〃が登場するからて、ある。このた char め , → signed char → signed signed char → singed signed signed char のように繰り返し置換が行われる。 しかし , ANSI 規格て、は , 「ひとたび置換 したマクロは , そのマクロの置換後の文字 列に同じものが再び登場しても , もはや置 換の対象とはしない」というルールが作られ た (ANSI 3.8.3.4 。原文はもっと長々した 表現だが , このように解釈してしまっても かまわないだろう ) 。このため , char を一度 signedchar に展開してしまうと , そこて、ス トップするのて、ある。このルールは , 一見 すると大したことはないように思えるかも しれないが , インプリメントは結構難しい 試しに , 複数の ANSI 準拠の処理系のプリプ ロセッサを使用て、きる方は , #define z #define g (x) x #define f(a) g(a + g(a)) f (z) というマクロを展開させてみてほしい。す べて同じ答えが得られたという方は , 非常に コンパイラに恵まれた , 好運な人だと思う。 ちなみに , 正解は , のようになるはずてある。 こうならないプリプロセッサは上記「再置 換の禁止」ルールの実装に手抜かりがあると 思われる。 マクロ呼び出しのメカニズムは , 原則と して「実引数」を展開し , その後に本体を展 開し , 本体のマクロ引数のところに , 実引 数の展開形を代入するという作業を ( 必要が あれば再帰的に ) 繰り返す。しかし , これに は例外がある。文字列化演算子の〃 , およ び , トークン連結演算子のヾ # # クが適用され ているマクロ引数に関しては , 実引数はマ クロ展開されずに , そのまま代入されるの てある。たとえば , 次のような定義て、考え てみよう。 #define F00 C #define BAR MAGAZINE #define catl (), y) x # # Y

5. 月刊 C MAGAZINE 1993年10月号

もスピードを重視するのか , コードあるい はデータサイズのコンパクトさを重視する のかなどを指示したり , あるいはデータ配 置のアライメントルールなどを指示したり て、ある。しかし , あくまて、これはコンパイ ラがそのような機能を持っているかどうか わからない に依存する。 ない」。何のためにあるのか , 筆者にはよく も定義されている。意味は「何も効果を持た ANSI 規格て、は , 、、 # 〃だけという「 null 指令」 この場合 , 最初の f00 は展開を受けない f00 ( ) f00 #define f00 ( ) BAR ものもあったと聞く。次の例を見てほしい。 サの中には , このような場合工ラーにする らず , 何の問題もない。古いプリプロセッ てはこのような場合にはマクロ置換は起こ 引数なして、記したらどうなるか。 ANSI 規格 引数を受け取るべきマクロの名前だけを実 に実引数リストなしに記したら , すなわち , また , 関数風マクロのマクロ名を , 直後 り , 許さないものもあった。 れは昔のプリプロセッサて、は許すものもあ わない ( 改行も空白文字の一種て、ある ) 。 おいてもよい。改行文字を挿入してもかま 名と実引数の間に任意の個数の空白文字を 口を呼び出すときに , ANSI 規格て、はマクロ たとえば , 細かいことだが , 関数風マク いろいろあるのだ。 サ」て、も , ある機能がて、きたりてきなかった ことはて、きない。つまり「古いプリプロセッ ロセッサ」という具合に割り切って話をする 「 ANSI プリプロセッサ」と「 pre-ANSI プリプ う仕様のものが存在した。だから , 一概に の中には , 細部においていろいろと食い違 なかった。このため , 古いプリプロセッサ ロセッサの仕様をあまり事細かに定めてい 冒頭にも述べたように , K & R て、はプリプ マクロ置換について 実引数がないからて、ある。しかし , 2 番目は BAR に展開される。また , 実引数の個数と マクロ引数の個数が食い違っている場合に はそれはエラーて、ある。この点は ANSI 以前 から普遍的に変わっていないと思う。 しかし , なんといっても , 昔からいろい ろ混乱がひどかったのは , 文字定数あるい は文字列定数の内側をマクロ置換の対象と するのかどうかという点と , もうひとつは トークンの連結がて、きるのかどうか , て、き るとしたらどうやってやればよいのかとい う点て、ある。 まず , 問題点を提示しよう。次のマクロ 定義を見てほしい #define F00 (x) puts ( ' ⅸ , world") このマクロを次のように呼び出すと , AN SI 以前て、は , プリプロセッサに依存して , 結果が 2 種類生じていた。 F00 (hello) ; → puts('hello, world") ; → puts ('\, WO 日 d つ ; すなわち , 文字列定数内部に , マクロ引 数と同じものが存在したときに , それを置 換するのかどうかの違いて、ある。同じこと は文字定数に関してもいえる。有名な例が ( ASCII 文字集合環境て ) コントロールコード を意味する定数式を生成するマクロて、ある。 #define CTRL (c) ('c' & OxIf) CTRL(A) → ('A' & 0x1f) → ('c' & 0x1f) これに関しては , ANSI 規格は「文字定 数 , ないしは文字列定数内部は置換しない」 と定めた。 なぜならば , ANSI 規格のプリプロセッサ は「トークンべース」に動作するからてある。 文字定数とか文字列定数というのは全体て ひとつのトークンて、あり , その内部にたま たまマクロ引数と同じ識別子として解釈可 能な部分文字列があったとしても , それは 認識しないという立場て、ある。 これに反して , 従来のそれらを置換対象 とするプリプロセッサは文字べースて動作 しているということになる。プリプロセッ サを独立したプログラムとして実装する場 合には , 文字べースという解釈のほうが楽 N mo てあるから , このようになっていたことは あながち非難て、きるものて、はない もうひとっ , トークンの連結問題がある。 これは , 二つのマクロ引数を連結してひと つのトークンを生み出すようなマクロを書 けるかという問題て、ある。 具体的には , 次のような動作を期待して いる。 cat (), y) というのがマクロてあると cat(LAB, 123 ) → LAB123 と展開してほしいのて、ある。 / * 明らかにダメな例 * / #define cat (), y) xy #define cat(), y) x y のように定義したのて、は明らかにダメて、あ る。 最初の例て、は , 展開結果は常に、、 xy クにな る。 2 番目の例て、は , 確かに x も y も展開され るが , 両者は空白て、区切られているから , 二つのトークンとなり , 期待に沿わない 古いプリプロセッサて、は , これを解決する 手法に二つのやり方があった ( 次に示す # if に よる振り分けは一例て、あって , 厳密にこれ て、常に大丈夫というわけて、はない ) 。 / * AN 以前に使われていた裏技 * / #ifdef BSD # define cat(), y) x* #else # define cat (), y) x/ * * /y #endif 要するに , 改行て、分離するか , コメント て、分離するかという手法て、ある。しかし , これらの手法はいずれも ANSI 規格て、は使え ない。そこのことはすてに読者はご存知の はずてある。なぜなら , トランスレーショ ンフェーズ 2 において , 「ノヾックスラッシュ十 改行文字」という形式は除去されて論理行が 構成される。この時点て x と y は連結されて ひとつのトークンになってしまい , 最初に 示した明らかにダメな例の 1 番目に等しくな る。 一方 , トランスレーションフェーズ 3 にお いて , コメントはひとつの空白文字に置き 換えられるのて , コメントて分離している ANSI C ー more 141

6. 月刊 C MAGAZINE 1993年10月号

立していたならば , 対応する # endif まて、をス こて、 , ひとつの キップすることになる。 #if/#ifdef/#ifndef に対して , # elif は 0 回以上 何度現れてもよいが , # else はたかだか 1 回し か現れることがて、きない。なお , これらの 条件コンパイル指令は , ネストさせて用い ることがて、きる。 さて , #if あるいは # elif において , 「定数式 を評価する」と述べた。このメカニズムは少 し補足しておく必要があるだろう。 まず , # if / # elif のオペランドはマクロ展開 が行われる。すなわち , マクロとして定義 されているものがオペランドの定数式の中 に記されていれば , それはその定義て、置き 換えられる ( ただし , 単項演算子 defined のオ ペランド部分は例外てあり , マクロ展開は行 わない ) 。 #include と並んて , #if/#elif のオ ペランドてはプリプロセッサ指令て、マクロ 展開が行われる例外のもうひとつの例てあ そして , すべての置換が行われた後に もし , まだ定数式の中に識別子が残ってい たならば , それは ( プリプロセッサ数の ) 0 と 置き換えられる。すなわち , マクロ名とし て定義していない識別子は 0 と評価されると いうことになる。 後は , 一般に定数式に許される演算て、あ れば ( sizeof を除けば ) , 自由に用いることが て、きる。定数式とは , 文法上は conditional -expression のことてあり , カンマ演算子と 代入演算子以外は用いることがてきる。な お , この「定数式」の評価て、は , 整数はすべ て long ( 符号なしは unsigned long) として演 算される。 さらにもうひとつ注意すべきことは , こての評価において , 文字コードがソース 文字集合て演算されるのか , 実行文字集合 て、演算されるのかは処理系依存だというこ とてある。つまり ,ANSI 3.8.2 から引用す れば , プリプロセッサにおける条件テスト てある , の結果と , C のソースにおける条件テストて #if 'x' = 25 ) ある , の結果の真偽が同じてあるという保証はな いということてある ( 蛇足だが , ASCII コー ド系て、はアルファベットの大文字・小文字 の文字コードはそれぞれ連続しているのて , この条件は成立するはずてある。一方 , EB CDIC て、はアルファベットの文字コードは途 中て飛んて、いて不連続なため , 上記条件は 成立しない ) 。 条件コンパイルについては , もうひとつ コメントしておこう。それは , 条件コンパ イルの開始については , #if/#ifdef/#ifndef と 3 種類が用意されているのに , # elif はひと つしかなく , #elifdef とか #elifndef はないと いう点てある。 これは , defined を用いれば , #elif だけあ れば用は足りるからて、ある。 defined はプリ プロセッサの定数式だけに存在する特別な 単項演算子てあり , オペランドには識別子 を取る。そしてその識別子がマクロ名とし て定義されていれば 1 を , されていなければ 0 を返す。これを用いると , #ifdef F00 → #if defined F00 #ifndef BAR → #if !defined BAR のように書き換えることがてきる (defined は 単項演算子なのて , オペランドを包むカッ コは必ずしも必要てはない ) 。そして , 存在 しない #elifdef/#elifndef の代わりに → #elif defined BAZ #elifdef BAZ #elifndef ZOT → #elif !defined ZOT のように書くことがてきる。 したがって , 実際には #ifdef/#ifndef も不 要て , #if と #elif, defined だけあればよい #ifdef/#ifndef が残っているのは , いわゆる 「歴史的事情」によるものてある。古いソー スとのコンパチビリティを考慮してのこと てある。 defined は , 複数のマクロ名が定義されて いるかどうかを調べて条件コンパイルを行 いたい場合には非常に便利てある。たとえ ば , ANSI というマクロと , UNIX というマ クロと , SYSV というマクロとがともに定義 されているときだけ , ある部分をコンパイ ルしたいと考えたとする。もし defined なし #ifdef ANSI ては , mo / * ANSI & & UNIX & & SYSV . # ifdef SYSV # ifdef UNIX #endif # endif # endif に示す形式になればよい 合には , それらのマクロを展開した後に次 ランドにマクロ呼び出しが記されていた場 マクロ展開の対象になる。このため , オペ が許される。ただし , #line のオペランドは この指令は , 原則として次の二つの形式 line て示せばよい (List 6 ) 。 対応関係を明示したい場合には , コメント 置き換えられてしまうからてある。だから ョンフェーズ 3 においてひとつの空白文字に るのに対して , コメントはトランスレーシ れるのはトランスレーションフェーズ 4 て、あ なぜならば , プリプロセッサ指令が解釈さ ただし , コメントを記すことはてきる。 ていたならば工ラーにしなければならない 改行文字との間に空白以外のものが記され なプリプロセッサて、あれば ,#else/#endif と は許さないことになった。 ANSI 規格に忠実 だが , ANSI 規格てはこのコンべンション を記すことて、対応関係を明示する習慣て、あ ef ) て記したと同じ識別子 ( ないしは定数式 ) の後に , 対応する #ifdef( あるいは #if/#ifnd うな記述て、ある。すなわち ,#else とか #endif れたソースてよく見かけるのが , List 5 のよ とくに BSD 系 UNIX の C コンパイラて、開発さ さて , #if/#endif などにおいて , 昔から , に簡潔に書くことがてきる。 しかし , defined を用いれば List 4 のよう 法上は別に入れなくてもかまわない ) 。 イングスタイル上の趣味の問題てある。文 てヾ # クの後に空白を入れているのは , コーデ のように書かざるを得ない ( ネストした内側 ANSI C ー more 139 こて , 「数字の並び」はソースファイル #line 数字の並びファイル名 #line 数字の並び

7. 月刊 C MAGAZINE 1993年10月号

- い d 町 0 町 0 動 00000 ロロ目 00 ロロロロロ とり早い 解決方法。 CDOC N mo ANSI 規格て、は , #define cat2(), y) catl (), y) #define bazl (), y) #x[x ー x # # y] 十 y ソースの現在行番号 LINE #define baz2(), y) bazl (), y) 現在のソースファイル名 FILE catl ( F00 , BAR) コンパイル時の日付 ( Mm DATE cat2 ( F00 , BAR) m dd yyyy) bazl ( F00 , BAR) コンバイル時の時間 ( HH baz2(FOO, BAR) MM : SS) の展開結果は , AN 準拠であれば 1 と定 catl ( F00 , BAR) → FOOBAR 義する cat2 ( F00 , BAR) → CMAGAZINE の 5 個のマクロ名を定義済みとしている ( ア bazl ( F00 , BAR) ンダースコアふたって、前後をはさんて、いる →下 00 " [C ー FOOBAR] 十 MAGAZINE 点に注意 ) 。 baz2 ( F00 , BAR) なお , LINE と FILE 以外は , ℃” [C - CMAGAZINE] 十 MAGAZINE 度コンパイルを開始すると , ずっと値が変 となる。 catl の場合 , 本体の内部て、マクロ引 化しないことが保証されている。ただし , 数にソ # 〃が適用されてしまうのて、 , 引数の この中て、 STDC がくせ者て、あることは 展開が行われない。しかし , cat2 て、は , ca 何度か述べた。「 ANSI 準拠」て、あるという判 t2 の定義本体には # クは登場しないのて、 , そ 断がいい加減て , 処理系によっては , ほと の時点て、実引数は展開され , C と MAGAZI んど ANSI 規格てありながら , 遠慮して S NE になる。これが catl に渡されるのて、あ TDC を 1 と定義せず , あるいは , 単にプロ る。この cat の例は K & R 第 2 版の解説にもて、 トタイプをサポートしただけの , 細部て、は てくるのだが , もともと原書ても文章が難 まったく ANSI 規格とは食い違う処理系て、も 解て、あり , 邦訳て、はさらに輪をかけて意味 堂々と STDC を定義していたりする。 不明の説明になっているのて、 , K & R 第 2 版の N 規格か決めていること , 邦訳を読むだけて、は理解するのは難しいと ー決めていないこと ANSI 規格ては , 従来に比べるとはるかに catl ( F00 , BAR) → catl (), MAGAZINE) きちんとプリプロセスの細部を定義した。 / * 引数を展開 * / しかしながら , それて、もなお , 意図的に決 → cat2 (), MAGAZINE) めていないことは残っている。これは , そ / * catl を展開 * / んな細部まて、細かく決めなくても , とくに 困らないから , インプリメンタの都合のよ CMAGAZINE / * cat2 を展開 * / いように解釈してもらえばよいという方針 からのようてある。たとえば , bazl と baz2 も同様に考えていただければ 理解て、きる。実引数を展開するかどうかは , #define K (a) a * A 本体て、それが、、 # 〃やや # # 〃のオペランドになっ #define A (a) K(a) K ② ( 9 ) ているかどうかて定まるのてあって , 本体 の中にそれらのオペランドになっているマ のようなマクロ定義の展開結果がどうなる かは , ANSI 規格ては意図的に示さない クロ引数と , なっていないマクロ引数が並 存すると , 同じマクロ引数て、ありながら展 になっている (ANSIRationale 3.8.3.4 ) 。 この場合 , 展開結果が 開後の結果が異なるということに注意が必 要てある。 2 * A ⑨ マクロ展開の最後に , ANSI 規格が規定し になるのか , た「定義済みマクロ名」について触れておこ 2 * 9 * A になるのかは定めないことになっている。 ドキュメントジェネレータ TIME STDC 自由に、手間をかけすに CDOC は、ワードプロセッサやエデイタなどを使っ て作成した書式に従って、ドキュメントを出力す るツールです。ドキュメントのレイアウトや出力内 容は、自由に設計できます。出力ファイルは、その ままワードプロセッサ、エデイタなどに読み込んで 印刷したり、加工が可能です。出力サンプルも 好評です。 【対応言語】 C ( K & 日または ANS 規格準拠 ) 【対応機種】 PC -9800 シリーズ ( ノーマルモード ) ¥ . および価格 MS - DOS ( ve 「 . 3.1 以上 ) 0K 日以上 J -31 圓シリーズ 日本語 MS-DOS(Ver. 3.0 ) 以上 EWS 48 圓シリーズ¥ 250. VAX シリーズ¥ 3.0 凹 . 上記価格は全て消費税は含みません。 CASPEC/c*< 橋造化プログラミング 開発ツール 分かりやすく。 【動作環境】 PC ーシリーズ ( ノーマルモード ) MS-DOS Ve 「 . 引以上 【定価】¥ . 网 ~ 消費税は含みません 人と情をのしルーかたち 日本コンごューター・システハ株式会社 〒ロ 5 東京都江東区東陽 5-29-3 東京本社プロダクト開発部 TEL. 03 ( 5632 ) 田 93 ( 直 ) FAX. 03 ( 5632 ) 0 円 4 く資料請求番号 143 〉 ANSI C ー more 143

8. 月刊 C MAGAZINE 1993年10月号

C 十十入門 enum 型は , 一連の整数の定数を定義する ときに用いられます。書き方は , enum タグ名 { 要素 , とします。タグ名は省略て、きます。要素は , 先頭から 0 , 1 , 2 という値を持ちます。 enum { F00 , BAR, ZOT } ; は , 保証 00 ま・ ; まり , a BAR a 十十 ; a と同じ結果になるとはいえないわけて、す。 がて、きてしまうと , a の値を表す enum 定数 が存在しないことが問題になります。 たとえ同じになったとしても , それは , た またまそういうコンパイラを使っているか 列挙型の各要素についてのループは , Pa らにすぎません。 scal 言語て、は , enum による型づけは , ときとして非常に ( F00 , BAR, ZOT) ; type TakO 煩わしいものとなります。実際のプログラ #define F00 0 var a ムの例を調べてみると , 型を有用にせず , #define BAR 1 begin 値を一律に int て、扱うという , 割り切った使 F00 to ZOT do ・ #define ZOT 2 for a い方がされていることが多いようて、す。い にほば同じて、す。 というように書けます。 わば , enum は整数定数をまとめて定義する この値は , 明示することにより , 変更て、 C 十十て、同様のことをしようとして , 手段としてしか活かされていません。【例題 きます。 for (Takoa=FOO ; a く =ZOT ; a 十十 ) enum { F00 , BAR 2 , ZOT } ; 14 】を読んて、考えてください enum のスコープは , 定義されている場所 と書くと , a 十十のところて、エラーになりま とすると , FOO : 0 , BAR : 2 , ZOT : 3 とい う値になります。つまり , 前の要素より 1 大 て、異なります。関数の中やプロックの中て、 す。もっとも , 十十演算子を多重定義すれ 定義されていればプロックスコープになる きい値を取るわけて、す。 ば , 工ラーにならないようにて、きます。し し , プロックの外て、定義されていればファ enum の定義にタグ名をつけると , 以後 , かし , やってみればわかりますが , 期待す そのタグ名を用いて , enum 型の変数を定義 イルスコープを持ちます。まだ説明してい るほど実りが多くありません。この場合に ませんが , クラスの定義の中て、 enum を定義 て、きます。 は , enum Tak0 { F00 , BAR, ZOT } ; すれば , クラススコープを持ちます。 for(inta=FOO; a く =ZOT; a 十十 ) enum 定数は内部リンケージて、す。て、すか F00 ; Tako a #define 定数と大きく違うのは , 型づけが ら , 大域的に用いるならば , ヘッダファイ というように int 型て、扱うのが普通て、す。 これは一種の妥協て、す。 されていることて、す。前述の Tako 型変数 a ルて、定義しておきます。これは #define 定数 には , FOO, BAR, ZOT のいすれかしか代 と性質が同じて、す。 まと Ⅲ日ⅢⅢⅢⅢⅢⅢⅢⅢⅢⅢ【」題 14 】ⅢⅢⅢⅢⅢⅢⅢⅢⅱⅢⅢⅢ 入て、きません。 enum Tako { F00 , BAR, ZOT } ; 異なるタグ名を持っ列挙型どうしの代入 関数 / 変数のリンケージと , モジュールの もエラーて、す。 Tako a = F00 enum lka {FUMIFUMI, 印 YO 印 YO } ; 実装とインタフェイスについて説明しまし a 十十 , た。次回は , いよいよクラスて、す。今回の / / a が BAR になることを期待 印 YOH 膨 0 , Tako b 話に続けて , モジュール化の観点から入門 これは可能ですか ? はエラーになります。 lllllllllllllllllllllllllllllllllll く解答 > ⅢⅢⅢⅢⅢⅢⅢⅢⅢ日ⅢⅢ することにします。今回の「モジュール = フ enum 型から , 整数型 (char, short, int, ァイル」に加えて , もうひとつ「モジュール = long) には変換て、きますが , 逆の変換は保証 不可能 クラス」という機構が導入されます。お楽し enum 型は整数型との相互変換カイきない されていません。つまり , ために , インクリメント ( 十十 ) やデクリメ F00 , みに は OK て、すが , ント ( ーー ) がて、きません。 なお , 今回の題材に用いた電卓プログラ ムについては , 松田氏による好連載「実践ア Tako a ルゴリズム戦略」 ( 本誌 ' 92 年 9 月号「再帰下 もしくは「時代おくれ ( アナク のうち , 右辺の計算は OK なのて、すが , 代入 は , 工ラー ロ ) て、す ! 」という警告になります。 降型構文解析」 ) を参考にさせていただきま するところて、 int → enum の型変換を必要とし また , した。字句解析 , 構文解析について , たい 強制的にキャストして , て , 工ラーになるのて、す。 ARM によると , (Tako)1 ; enum 型は数値演算について閉じていないの へんわかりやすく説明されていますのて、 , Tako a とすれば , 工ラーになりませんが , が理由だそうて、す。つまり , 結果は ぜひご一読ください = ZOT ; Tako ; 110 C MAGAZINE 1993 10

9. 月刊 C MAGAZINE 1993年10月号

モリが十分に残っていないといけません。 TabIe 1 プログラムのロードと非実行 DOS ファンクション 4B01h はプログラムを DOS ファンクション 4B01h (DOS 2.1 1 ~ 5.0 ) 引数 AH =4Bh 起動する DOS ファンクション 4B00h とほとん AL = 01 h ど同じことを行いますが , プログラムを口 DS : DX = 起動する ASCIZ ノヾス名のアドレス ES : BX = ノヾラメータブロックのアドレス ードするだけて、実行せすに戻ってきます。 INT 21 h 返り値キャリー SYMDEB, CodeView などのデバッガが本 AX = 01 h : 無効なファンクション ファンクションを使用しています。 = 02h : ファイルが存在しない = 03h : 無効なノヾス パラメータブロックの内容は次のとおり = 04h : オープンされているファイルか多すきる = 05h : アクセスの拒否 て、す。 = 08h : 十分なメモリがない ①渡される環境のセグメントアドレス =OAh : 不正な環境 =OBh : 不正なフォーマット 子プロセスに渡す環境変数のセグメント キャリー = 0 ノヾラメータブロック十 OEh SP の初期値ー 2 アドレスを示します。 0000h の場合 , 親プ ノヾラメータブロック十 10h SS の初期値 ロセスの環境をコピーします。子プロセ ノヾラメータブロック十 12h 甲の初期値 ノヾラメータブロック十 14h CS の初期値 スに渡す環境変数は 32K バイトを超えては SS : SP 初期値ー 2 起動時の AX 初期値 いけません。 機能 DS : DX レジスタにロードするバス名の ASCIZ 文字列のアドレスを指定し , ES : BX レジスタ にロードするためのバラメータブロックのアドレスを指定する ② PSP のオフセット 80h のコマンドラインに DOS ファンクション 4B00h と異なる点はプログラムをロードして実行せすに戻ること バラメータブロックの内容は以下のとおり 対するポインタ オフセット バイト数 内 子プロセスに渡すコマンドラインに対す 2 渡される環境のセグメントアドレスか 00h の場合 , 親環境をコピーする 00h るポインタを示します。コマンドライン 4 PSP のオフセット 80h のコマンドラインに対するホインタ 02h コマンドラインの形式は以下のとおり の形式は Fig. 1 のとおりて、 128 バイトを超 えてはいけません。リダイレクト記号 コマンドライン文字列 CR ( < , > ■ ) はすべて親プロセスが処理す CR を除く文字数 ( 1 バイト ) る必要があります。 ③ PSP 5Ch に渡すデフォルト FCB に対する 4 PSP 5Ch に渡すテフォルト FCB に対するポインタ 4 PSP 6Ch に渡すテフォルト FCB に対するポインタ ポインタ 常駐サイズ 0 を実現する子プロセスのオーバレイ関数 ↑ 06h OAh LiSt ist プログラムのロード く string. h> 1 : $include 2 : # i ncl ude く s t d ⅱ b. れ > 3 : # i ncl ude く process. h> 4 : $include く dos. h> 5 : $include ” dosfunc. h ” 6 : TURBOC_ 7 : # i fdef dos_freemem 8 : $define 9 : VOid _restorezero( VOid 10 : extern Ⅱ : $endif : #define PRGEND 0x0a 14 : $define PARSEG 0X16 15 : $defi ne ENVSEG 0x2c $defi ne FILETBL 0X34 16 : 0x5c 17 : $define FCBI 0x6c 18 : $define FCB2 19 : long huge *PS. unsigned parasiz ) 20 : static VOid paracopy( long huge *pd, 22 : 23 : 24 . 25 : 26 . 1 : $include く dos. h> 2 : #include ” dosfunc. h ” 3 : dos-loadprogram( char far 4 : unsigned *name, void far *parablk ) 6 : 8 : 9 : 13 : 15 : 16 : 17 : union REGS regs; struct SREGS segs; ニ FP_OFF( nane ) : / * ds:dx = 起動する ASCIZ ハ。ス名のアト・レス * / regs. X. dx : FP_SEG( nane ) ; segs. ds = FP_OFF( parablk ) ; / * es:bx = ハ。ラメ - タ・フ・ロックのアト・レス regs. X. bX : FP_SEG( parablk ) : segs. es ニ 0X4b01 : regs. X. ax i ntdosx( &regs, &regs, &segs ) : if ( regs. x. cflag ) return( regs. x. ax ) : return( 0 ) : freemem の領のの 境・イ - a_ a. 仆親環万門門 / * フ。 0 ク・ラムの 0- ト・ / * MS-DOS のエラ - ・コ - ド / * ハ。ラク・ラフ単位の北。 - vhile ( parasiz- *pd 十十 *ps 十十 ; *pd 十十 *ps 十十 ; *pd 十十 *ps 十十 ; *pd 十十 *PS 十十 : 92 C MAGAZINE 1993 10

10. 月刊 C MAGAZINE 1993年10月号

ズ 7 以降をプリプロセスと考えるのは明らか に不適当て、ある。問題はフェーズ 5 とフェー ズ 6 て、ある。これをプリプロセッサの仕事て、 あると考えるかどうかは微妙なところて、あ る。何も規定されていないのだから , この 点については , 処理系独自の判断を行って よいということてある。 NS プリプロセッサ指令 それて、は , ANSI 規格が規定しているプリ プロセッサ指令をひと通り説明していこう。 まず前提として , プリプロセッサ指令とは どういったものて、あるかを説明しておこう。 ANSI 規格が定めるプリプロセッサ指令の定 義をわかりやすく述べると , 、、 # クというプリプロセッシングトークンて、 始まる , プリプロセッシングトークンの 並びて、あり , 、、 # 〃はファイルの先頭て、あるか , あるいは 改行の直後に存在し ( すなわち , 行の先頭 に存在し ) , ・そして最後には改行文字がきていること。 ・ただし , 〃の前後には空白文字 ( 改行文 字を除く ) があってもかまわない ということになっている。古い C のプリプロ セッサの一部には、、 # 〃の文字はまさに各行の 先頭の位置に存在しなければならないとし ているものがあったのだが , ANSI 規格て、は 先頭に空白を入れることも許される点が要 注意て、ある。ただし , コーディングスタイ ル上から , 、、 # 〃を先頭に置くことを薦めてい る人は多いようて、ある ( 筆者もそのように心 がけている ) 。 #include おなじみのインクルード指令て、ある。 Ta ble 1 に示したトランスレーションフェース て、は , とくにフェーズ 4 にこのプリプロセッ サ指令の処理方法が明記されている。ただ し , ヘッダファイルと呼ばれるものは , 必 ずしもソースファイルてなくてもかまわな ことになっている (ANSI 4.1.2 ) 。たとえ ば , あらかじめ標準ヘッダは内部形式に変 換して記憶してあって , 標準ファイルの #in ーの概観 clude を見た時点て、 , 処理系内部のシンポル テープルに , 別途記憶しておいた内部形式 の標準ヘッダの内容を追加するというよう な処理をしてもかまわない なお , 一般に、、 # クて始まるプリプロセッサ 指令に関しては , マクロ展開は行わない しかし , # include はその規則の例外のひとつ て、あり , ファイル名指定部分にマクロ名を 指定することは可能て、ある。 #define STDIO く stdio. h > #include STDIO と記すことがてきる。ただし , < stdio. h > と いうのはひとつのプリプロセッサトークン なのて、 , #define STDIO stdio. h #include く STDIO > のように記すことは許されない ( プリプロセ ッサトークンの解釈は分脈依存てある。 の場合には , # include のオペランドというこ とて特別に解釈される ) 。 この場合 , <STDIO> というのがひとつの プリプロセッサトークンて、あり , ヘッダフ ァイルの指定て、あると認識される。 < STDI O > の中の STDIO はマクロ置換されない ( も し , 類似のことを行いたければ , トークン の連結というマクロ演算子を用いればよい これについては後述 ) 。もちろん , <STDIO> という名前のヘッダは ( とくに処理系ローカ ルなヘッダとして定義していないかぎり ) 存 在しないのて、 , これはエラーてある。 define マクロの定義を行う。マクロには 2 種類あ る。ノヾラメータのないマクロと , ノヾラメ タっきのマクロてある。前者は「オプジェ クト風マクロ (object-like macro) 」と呼ば れ , 後者は「関数風マクロ (function-like macro) 」と呼ばれる。 それぞれの例を List 1 に示す。 マクロ名 , とくにオプジェクト風マクロ の名前は習慣的に大文字だけを用いること が多いが , もちろん文法上は何の制約もな い。関数風マクロの場合 , マクロ定義にお いては , マクロ名と引数を囲むカッコ ( 左カ ッコ ) の間に空白を入れてはならない。すな mo わち , #define f00 (x) ((x) 十 1 ) は関数風マクロ fo 。の定義て、あるが , #define bar (x) ( (x) 十 1 ) は関数風マクロの定義て、はなく ,bar は $(x) ( (x) 十 I) クと定義されたオプジェクト風マ クロの名前になる。また , 逆に , マクロ名 の直後にカッコを置くならば , それはマク ロの引数リストて、なければならない ( 識別子 以外のものが引数リストの中に指定されて いた場合の挙動は処理系依存て、ある ) 。マク ロの引数リストは , 0 個以上の識別子をカン マて、区切って並べたものてある。 , こて、注意すべきは , マクロ引数が 0 個て、 あっても , カッコが記されていれば , それ は関数風マクロて、あり , オプジェクト風マ クロとは区別されるということて、ある。 の区別はマクロ呼び出しの形式に関係して くる。ただし , マクロの名前空間はひとつ しかない。したがって , オプジェクト風マ クロと関数風マクロて、同じマクロ名を同時 に定義するということはて、きない なお , マクロ名もまた識別子て、ある。 こて、いう識別子の構成規則は , C 言語そのも のの識別子の構成規則と同じて、 , 英字 , も しくはアンダースコアから始まり , その後 に任意の個数の英数字 , ないしはアンダー スコアをつないだものて、あり , ただし先頭 の 31 文字だけが有効て、ある。また , 引数の 個数が可変のマクロ (C の printf のような ) を 定義する方法はない マクロ呼び出しと , その展開に関しては こて、は最後に , マ 後て、論じることにし , クロの再定義の問題に触れておこう。 ANSI 規格て、は , 定義済みのマクロを再定 義する場合には , 元の定義と実質的に同じ 定義を与えなければならない。「実質的に同 じ」とは , トークンの間に空白文字を置くと きに , その個数が 1 個以上て、あれば何個て、あ っても同じ「空白による区切り」とみなすと いう程度の違いしか許さないということて ある。 関数風マクロの場合には , マクロ引数の 名前 ( 仮引数の名前 ) まて、も同じて、なければ いい換えれば , 定義済みのマク ならない ANSI C ー more 137