7-Feb-1994 きだ あきら SDI00379@niftyserve.or.jp LSI C-86 用改造版 cpp 解説書、あるいは与太話 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cpp for LSI C-86 Version 1.5k1(beta10) お忙しい方のための注: 以下のドキュメントの全部に目を通す必要はありませんが、ぜひ最後の 「注意」の部分だけは読んでから本プログラムをお使い下さい。 概要: LSI C-86 のプリプロセッサ cpp(kmori 版)は、いくつかの点で ANSI の規格とは異なった動作をします。それでも大部分の用途ではこれで充分 なのですが、やはり時として困ることがあります。幸いにも、LSI C-86 の製品版には cpp のソースが付属しており、かつそれは著作権フリーの 指定がなされています。そこで、ソースを修正することで ANSI 準拠にで きないものかと、無謀な試みを行ってみました。 結果的にできあがったこのバージョンは、オリジナル kmori 版に比べ れば、かなり ANSI に近い仕様になっていますが、完全な ANSI 準拠では ありません。kmori 版 cpp の枠組みを大きく変更しない範囲で可能な限 り ANSI の規格に近付ける努力はしましたが、やはり凡人である改造者 (K君=きだ)の能力には限界があります。 背景: LSI C-86 の cpp は一部 ANSI と仕様が異なっているため、時として不 便を感じていました。そこで、兼ねてから修正をしたいとおもいっていま した。実際、どうしても耐えられなかった部分だけは、'91 の秋に改造を 施して使っていました。ただ、その時点では ANSI フルコンパチにするつ もりはありませんでした。 そんなときにちょうどタイミング良く LSI Japan 社の pnori 氏(乗松 保智先生)と新年会('92)で呑む機会があり、その時同氏が「きださん、 cpp を改造して ANSI コンパチにしましょうよ」と唆したこともあって、 自分の能力の限界を知らない身の程知らずのK君は、鼻歌混じりで改造作 業を開始したのです。いざ手を付けるまでは、ほんの少し修正すれば ANSI フルコンパチになるだろうと思っていたからです。 しかし。まずオリジナルの華麗なコーディングの壁が大きく立ちはだか りました。kmori 氏(森 公一郎氏)のコーディングは正に匠の技を地で 行くもので、とにかく素晴らしく無駄が無いのです。なによりも、余りに 完全なコーディングは変更することを拒んでいるかのようです。あのよう なプログラムを見ていると、下手な修正はオリジナルに対する冒涜である ようにすら思えてきます。 時としてコードをいくら眺めても原作者の意図が読めず、真意を探るた めにトレースコードを入れてみたり、ほんの簡単な改造だと思っていたと ころが、実はロジックの根幹を覆すような改悪だったりで、いろいろつま づいては連日徹夜のデバッギングを重ねることになったのです。 しかも。K君の本当の不幸が始まったのはその後でした。一旦完成した と思って喜んだのも束の間、ANSI の規格書のサンプルマクロ定義を処理 してみると、結果が全然違うではありませんか。さすがにオリジナルの kmori 版よりは ANSI 規格に近づいていましたが、MSC v6.0 には忠実度 で大きく劣っている[*1]ことが判明したのです。ということで、更に規格 に忠実な処理系を目指してK君のいばらの道は続いたのです。 [*1] ただし、その後の調査で MSC v6.0 はいくつか不審な動きを することが判明しました。その内の幾つかは後に取り上げま す。 kmori 版 cpp の ANSI との違い: kmori 版 cpp で、実用上 ANSI とは異なった動作をして問題になる可 能性が高いのは以下の諸点でしょう。ほとんどがマクロ展開に関連した問 題です。 ・マクロ呼び出しの解釈が違う。 kmori 版において、引数付きで定義された関数風マクロを呼び出す場合、 マクロ名と引数を囲むための左括弧の間には、タブやスペースを置くこと は許されます。しかし、両者の間に改行がある場合には、それはマクロ呼 び出しとは認識されなくなります。ANSI 規定では、タブやスペースと同 様に、改行があったとしても、それはマクロ呼び出しとして認識されなけ ればなりません。 ・マクロの中で # や ## が使われた時の、置換ルールが違う。 ANSI になって、プリプロセッサに新しく文字列化の前置演算子「#」や、 プリプロセッサトークンの連結を行う中置演算子「##」が追加されました。 これらの演算子は、kmori 版でも一応サポートされてはいます。ただし細 かく見ていくと、それらのオペランドにマクロ引数が登場した場合の置換 ルールが異なることが分かります。 ANSI では、一般にマクロ呼び出しに 渡した実引数は、呼び出されたマクロ本体の展開に先だって評価されると いう基本ルールがあります。このため、もし実引数の中にマクロ呼び出し があったならば、それは展開されて、展開後のトークン列がマクロ引数に 代わって置換されます。ところが、このルールには例外があるのです。そ れが # や ## のオペランドにマクロ引数が指定された場合です。この場 合、そしてこの場合に限っては、マクロの引数には、あらかじめマクロ展 開されずに、引数として受け取ったトークン列がそのまま代入されるので す。 このルールはややこしいので、実例で説明します。次の例を見てくださ い。 /* * # と ## のオペランドのマクロ引数の置換 */ #define foo(arg) arg + soft ## arg(#arg) #define SOFT bank foo(SOFT) /* bank + softSOFT("SOFT") */ この例では関数風マクロ foo を定義しています。foo は引数「arg」を 受け取ります。マクロの本体は、次のようになっています。 arg + soft ## arg(#arg) ここで、先頭の arg は通常のマクロ引数の参照ですから、ここは、実 引数をマクロ展開した後のトークン列によって置換されます。次の soft は単なる文字列定数(トークン)です。二番目の arg はトークン連結演 算子「##」のオペランドですから、ここは実引数がマクロ展開を受けずに、 実引数に記されていたトークン列そのもので置換されます。三番目の arg も文字列化演算子「#」のオペランドですから、同様に実引数はマクロ展 開されず、そのままのトークン列がダブルクオートでくくられた形で置換 されます。ですから、Test 2 の中で foo(SOFT)と呼び出している部分は、 結果的に次のように展開されなければなりません。 bank + softSOFT("SOFT") ところが kmori 版では、「#」や「##」のオペランドであろうがなかろ うが、無関係に実引数のマクロ展開を行ってしまいます。このため、kmori 版では次のような展開結果となります。 bank + softbank("bank") ANSI のマクロ引数の置換ルールの下で、実引数のマクロ置換を済ませ た後に文字列化とかトークン連結を行いたい場合には、マクロを二段階に 定義してやらなければなりません。以下に例を示します。 /* * # と ## のオペランドのマクロ引数の置換 */ #define foo(x) #x #define bar(w) foo(w) #define baz(y, z) y ## z #define zot(a, b) baz(a, b) #define HELLO ANSI #define WORLD _C foo(HELLO) /* "HELLO" */ bar(HELLO) /* "ANSI" */ baz(HELLO, WORLD) /* HELLOWORLD */ zot(HELLO, WORLD) /* ANSI_C */ foo と baz は、マクロ引数に直接「#」や「##」を適用しているために、 実引数はマクロ展開を受けずに置換されます。しかし bar や zot は、一 度実引数がマクロ展開を受けた後に前記 foo や baz を呼び出すことにな るため、その時点で実引数の値はマクロ定義に置換されることになり、結 果的に実引数をマクロ展開した後の値に対して文字列化やトークン連結を 行うことになります。 ・文字列化時のエスケープが行われない。 実用上の観点から見ると、この点が kmori 版ではもっとも問題になる ところでしょう。プリプロセッサの文字列化演算子「#」は、簡単に言え ば「実引数の両側をダブルクオートで囲む」という働きをするものです。 だがこの演算子の働きは、厳密に言えばそれだけには留まりません。以下 の例を見てください。 /* * # による文字列化の問題 */ #define str(x) #x str(Hello) /* "Hello" */ str("world") /* "\"world\"" */ str("\n") /* "\"\\n\"" */ 文字列化を行うマクロ str(x)を定義しています。実引数として、たと えば Hello が渡されると、これは "Hello" と展開されます。それでは、 実引数が "world" であったらどうなるでしょうか。この場合には、world という単語の両側にダブルクオートが付いていいる、その全体をデータと して含む文字列が展開されなければなりません。すなわち、ダブルクオー トはバックスラッシュでエスケープして、次のように展開される必要があ ります。 "\"world\"" また、実引数として、ダブルクオートの間にバックスラッシュを含む、 「"\n"」のようなトークン列が渡されることもあるでしょう。その場合に は、バックスラッシュをバックスラッシュ自身でエスケープして次のよう に展開しなければなりません。 "\"\\n\"" これらの処理は、kmori 版ではいずれも行われません。 ・文字列化の際のトークン間のスペーシングが違う。 ANSI では、プリプロセッサはトークンベースと文字列ベースの間の妥 協を図った仕様となっています。すなわち、基本的にはプリプロセッサは、 プリプロセッサトークンと呼ぶ字句的なかたまりを単位に処理を行い、トー クンとトークンの間には任意個の空白類文字(タブ、スペース、改行など) があっても構わないという仕様になっているのです。ただし、この仕様で 問題になるのが文字列化演算子です。文字列化演算子では、最終的に実引 数で与えられたトークン列を文字列へと変換します。言うまでもなく、文 字列の内側では空白文字が何個あるかは意味を持ちます。そのため、文字 列化演算に限っては、実引数で与えられたトークン列の間の空白をどう処 理するかが問題になるのです。 ANSI の回答は、トークン間に空白類文字 が1つ以上存在していたら、文字列化に際しては、常にただ一つの空白文 字に置き換えるというものです。空白類文字が一つもなければ、結果にも 空白は現れません。空白類文字が一つ以上あれば、どんなにたくさんあっ ても、文字列化後にはただ一つの空白文字に置き換わるのです。kmori 版 では、この処理も行われていません。 ・マクロのリスキャン時の展開抑制が違う。 これがまた、極めてやっかいな規定です。すなわち、ANSI が新しく採 用したルールとして、あるマクロがひとたび展開された場合には、展開後 のトークン列に同じマクロの呼び出しであると解釈できる部分があっても、 もはやそのマクロは展開しないことになっているのです kmori 版でもリ スキャン時の抑制は一応行っていますが、それは一つのマクロの展開を行 う間に限られ、ANSI 通りにはなっていません。ANSI の規定では、この展 開抑制は、複数のマクロにまたがって有効でなければならないとされてい ます。ただし、このルールの差異が問題になるのは、かなり意図的にトリッ キーなマクロ定義を行った場合に限られるので、あまり実用上の問題には ならないかもしれません。しかし、現実にはあまり問題にならないかもし れないとはいえ、ANSI 準拠をうたうためには、このルールをインプリメ ントせざるをえません。だが、これは一筋縄では行かないルールなのです。 それでは、このルールのインプリメントがいかに面倒かを実感するため に、次の、何気ないけれど、かなり作意的なサンプルを考えてみましょう。 /* * Sample #1 */ #define z z[z] #define g(x) x #define f(a) g(a + g(a)) f(z); このファイル(test.c)を与えてプリプロセッサを起動すると、MSC v5.1 および QCC v2.0 では、なんと次のようにエラーになってしまって処理が 中断してしまいます。 test.c(4) : fatal error C1056: compiler limit : out of macro expansion space さらに、MSC v6.0 では不審なことに、上と同じエラーが出ることもあ り、なんだか訳のわかない結果を出力することもあります。しかも動作さ せる毎に処理結果が違ったりするという症状にも出くわしました。 また、Lattice C V4.11J ではこうなりました。 z[ + z[; これらは、一見しておかしいということが分かります。 では LSI C-86 の kmori 版 cpp ではどうなるかというと、エラーになっ たりはせず、次のように展開します。 z[z][z[z]][z[z][z[z]]] + z[z][z[z]][z[z][z[z]]][z[z][z[z]][z[z][z[z]]]]; なんとなく z が無闇に出てくるのが嘘っぽいですね(^_^;)。実際これ は規格の通りではありません。 さて、真打ち gcc に登場を願いましょう。gcc v1.40 の cpp ではこう 展開されました。 z[z] [z[z] ] + z[z] [z[z] ] ; 残念ながら、これも ANSI 違反なのです。もっとも gcc の場合には ANSI フルコンパチにしようという意図が無いのかもしれませんが。 手元の処理系では、TC++ v1.01 が ANSI 通りの正解を出します。 z[z] + z[z]; では、TC++ v1.01 の cpp が常に正しいのかというと、残念ながらとい うか、やはりというか、「フッフッフ、甘〜ぁい」のです(^_^;)。今度は 次のファイルを TC++ v1.01 で展開してみましょう。 /* * Sample #2 */ #define g(x) x + f(x) #define f(a) g(g(a)) f(z); どうも、TC++ のプリプロセッサは、こういう交互再帰呼び出しを考慮 していないようでして、あっさりギブアップします[*2]。ちなみに、これ に関しては MSC v6.0 は正しい答を出します。 [*2] 暴走する場合もありえますので、RAM ディスクなどお使いの 場合にはバックアップを取ってからテストしましょう。 これらのマクロ展開における最大の問題は、最初に例を上げた「一度展 開したマクロは再スキャン時には再び展開しない」というルールです。こ のルールを正しく守っているかどうかで、サンプル1、2の結果が正しく 得られるかどうかが定まります。本プログラムは、当然これらに対しては 正解を出すようになっています。 * さて、これらの問題をクリアした後に待ちかまえていたのが、次のよう なマクロが展開できるか?という疑問でした。いや、これに関しては、 「展開できるべきなのかどうか」が疑問だとも言えます。 /* * Sample #3 */ #define foo(a, b) [b+a] #define bar(a) foo(a #define baz(b) ,b) #define zot(a, b) a b zot(bar(1), baz(2)) このサンプルを与えた場合、MSC v6.0, TC++ v1.01, gcc v1.40 の3者 は共にまともな答を返してくれません。大体、普通の人がこんな不気味な マクロを使うはずはありません。しかし潔癖性のK君は、こういうマクロ もそれなりに展開できるべきだと考えて、余計な部分で苦労をしたのでし た。 ちなみに ANSI では、このようなビョーキのマクロがどのように展開さ れるかに関しては、意図的に規定しないという方針のようです。 * * * 主たる改造項目: 以上に述べてきたような kmori 版の問題点を考えて、本改造版 cpp で 変更を加えて新規にサポートした機能を以下に示します。 ・トライグラフのサポート(オプション -T で選択。デフォルトはオフ)。 ・マクロコール時に、マクロ名と引数を囲む括弧の間に改行を含めて任 意の空白の存在を許す。 ・# と ## が適用されているパラメータに関しては実引数のマクロ展開 を禁止する。 ・それ以外のパラメータに対する実引数の展開は、パラメータへの代入 の直前のタイミングで行う。 ・# で文字列化する場合に、実引数が文字列や文字定数であったならば、 そこに含まれる " や \ を \ でエスケープする。 ・## に関しては、「## ##」など、無意味に思えるシーケンスも一応もっ ともらしく動作させる。 ・マクロ展開後のリスキャンでは同一のマクロは、たとえネストしたマ クロ呼び出しで間接的に評価されていても、再度展開はしない。 ・マクロ展開後に「#」で始まる、マクロ指令とおぼしきトークン列が 得られても、それらをマクロ指令とは解釈しない。 ・改行文字の直前に「\」が打たれていたら、マクロ処理に先だって次 の行との連結をする。 ・相続くトークン間の空白/改行はただ一つの空白に置き換える。 ・マクロ展開後に複数のトークンが一つに合成されてしまうのを極力防 ぐ。 この中で、先に述べた kmori 版と ANSI との差異の説明では触れてい なかったトライグラフに関して、若干補足を加えておきます。トライグラ フとは、??で始まる三文字のシーケンスで、使用している計算機が採用し ている文字セットによっては含まれていないかもしれない特定の文字の代 用として用いる機能です。たとえば、「??<」と記すことで「{」と書いた のと同じ意味になり、「??>」と記せば「}」と同じ意味になります。とい うのも、「{」や「}」は ASCII キャラクタセット(や、それを基準にし た ISO キャラクタセットや JIS キャラクタセット)には含まれています が、それらの文字を含んでいないキャラクタセットを用いている計算機 (主として、一部メインフレーム)も存在しているからです。 ANSI-C 標準に忠実なコンパイラでは、このトライグラフが使える状態 になっていなければなりません。しかし、LSI C-86 にはトライグラフの サポートは行われていません。実際には、トライグラフは嫌われものです。 これをサポートしているコンパイラであっても、オフにするオプションを 持っていることが多いようです。というのも、現在広く使われている WS 以下のマシンでは(当然 MS-DOS マシンはこのジャンルに含まれる)、 ASCII キャラクタセットをサポートしているのが普通で、わざわざトライ グラフを利用してソース内に変な記述を行う意味はないからです。それよ りも、「??」で始まるシーケンスが文字列の中に存在した場合、それらが トライグラフであると解釈されてしまって、思わぬ結果になってしまうと いう「害」の方が大きいと考えられています。とは言え、一応 ANSI に忠 実であろうとするならば、どこかでトライグラフをサポートできる道を付 けておくべきだと考えました。そして、もし LSI C-86 でそれを行うなら ば、cpp の中の一文字入力部分でその処理を行うのが賢明であろうと判断 しました。そのため、本改造版には、その機能を付加しています。ただし、 これはデフォルトではオフになっていますし、また LSI C-86 コンパイラ ドライバ lcc を標準のまま使用している限り、このスイッチをオンにす る術はありません(詳しくは後から出てくる「使用にあたっての注意」の 項参照)。 主な改版歴: 29-Oct-1991 LSI C-86 v3.30 に付属のソースを元に改造を施す。そ もそもは、# オペレータで文字列化するときに、実引数 の文字/文字列に含まれる「\」(半角の円記号…本来 はバックスラッシュ)をエスケープしてくれないことか ら改造を思い立った。この時点では ANSI フルコンパチ にするということは考えていなかった。トライグラフも 付けてみたが、実質的に常に無効になっている(lcc の 改造が必要…本文書末の「注意」参照)。 15-Jan-1992 NIFTY-Serve FPL の新年会で pnori 氏に「ANSI フルコ ンパチにしましょう」と唆され、ついついその気になる。 が、ANSI への道は遠くて険しかったのである。特に苦 心したのが1度展開されたマクロは再スキャン時に展開 しないルールの実装である。大変苦労したのだがこの時 点での実装はまだ ANSI に忠実ではないものだった。 18-Jan-1992 マクロのスリープ機構を変えた。これでほぼマクロの展 開に関しては ANSI コンパチになったはず。これまでの 実装は、論理的にも美しくない部分があったため、単純 で明快な今回の構造とした。 22-Jan-1992 マクロの引数部分で、(別な)マクロの呼び出しと紛ら わしいが、正しいマクロ呼び出しではない形式が存在し た場合に、即刻エラーで落ちていたのを修正した。ただ し、これは ANSI 規格が要求する処理であるのかどうか は不明である[*3]。しかしながら、MSC v6.0、TC++ v1.01 などはこのようなケースでは不審な挙動を示すので、 robustness では本プログラムの方に軍配が上がると思 う。 [*3] 調べた限りではこの点に関しては規格書には記されていない。 23-Jan-1992 ## ## など、一見無意味な記述でも、なるべくもっとも らしいと感じられる結果が得られるようにした。 08-May-1992 マクロ展開後のトークンの再融合を防ぐようにした。た だし、これは現在意図的に 100% 完全に作動するように していない。ANSI の規格書の出力例が矛盾しているか に見えるため、この辺りで妥協するのが得策と考えてい る。 24-Apr-1992 行がバックスラッシュで終わっていた場合に、ANSI に したがって、まず行の連結をするように変更(ANSI の 仕様書を読み返していて、初めてこの点に気付いた)。 28-Apr-1993 ANSI とは関係ない[*4]が、opey のコンパイルができる ように #include のネストを増やした。ついでに #if 〜 #endif のネストも増やした。 [*4] ANSI によれば、ネストは #include、#if ともに少なくとも 8 レベル保証すれば良い。したがって、kmori 版の 8 とい う数字は ANSI 準拠なのである。 18-Sep-1993 C マガジンの付録で公開するということで、かなり昔に 半分書き上げてあった本ドキュメントの体裁を整える (本ドキュメントの中で、cpp の動作結果の比較例とし て取り上げているコンパイラが、若干 out of date なの はこのような理由による)。 06-Feb-1994 再び C マガジンの付録に添付することになり、ドキュメ ントに若干手を加えた。また cpp 本体も、一部のエラー メッセージを、より適切なものへと変更した。 明白に ANSI 規格に沿っていない点: (1) プリプロセッサトークンの取扱いのルールが ANSI とは異なる。特に pp-number トークンの取扱いは ANSI に準拠していない。とはいうも のの、kmori 版と比較すると、若干は ANSI に近づけてはいる。しか しながら、ANSI のルールをきちんと全面的にインプリメントするの は相当面倒であるため、中途半端でさぼっている。幸か不幸か、この 食い違いが現実的に問題となるような(コンパイルエラーにならない) C ソースは思いつかないので、当面無視する。 (2) ANSI の規定では、同名のマクロを再定義するときに、実質的に内容 が同じでなければならない(だから、現実には #undef することなく、 あるマクロを再定義して内容を変更することはできない)。とくに、 マクロの仮引数の名前はマクロ定義の中で意味を持つことになってい る。このため、仮引数名の違い以外は全く同じ定義であっても、再定 義のエラーにしなければならない。たとえば次の定義・再定義のシー ケンスは、仮引数名が異なるのでエラーである。 #define foo(x) ((x) + (x)) #define foo(y) ((y) + (y)) この(いささか納得行かない)規定もインプリメントしていない。本 プリプロセッでは仮引数名の相違は無視され、実質的な定義内容の差 異のみが問題となるような仕様になっている。このため、foo(x) と foo(y) の例では再定義してもエラーにはならない。ただし、マクロ 定義本体の中のトークン間の空白類文字の個数などは ANSI に従って チェックしている。したがって、再定義に際して空白類文字を挿入/ 削除してはならない。その場合には、やはり再定義エラーになる。 * * * 免責: このプログラムは有用[*5]であれという希望の下に無保証で配布するも のであります。明示されている/いないを問わず、どのような目的に関し てであれ本プログラムの機能は保証されていません。勝手ながら、本プロ グラムを利用された結果直接・間接に引き起こされた損害に対して、改造 者は一切の責務を負わないことをお断りしておきます。 [*5] 実の所、このプログラムがオリジナル cpp に比べて、「よ り有用」かどうかはかなりあやしいです。 著作権: 本プログラムは LSI C-86 v3.30(製品版)に付属していた、kmori(森 公一郎)氏作の cpp.c のソースを元に改造を施したものです。改造部分 の著作者は、きだあきら(SDI00379 @ NIFTY-Serve)です。 LSI Japan 社は、LSI C-86 のマニュアルの中で、当該ソースファイル の取扱いに付いては、商利用を含めて配布・改造に関する制限を一切設け ない旨を明記しています。また、改造者も、改造部分の著作権を主張しな いことをお約束します。したがって、本プログラムの取扱いに関する一切 の制限はありません。第3者への譲渡・改造・ネットワークサービス(BBS) への掲載・出版物へ添付しての配布等を始めとして、商利用を含めて、本 プログラムの全体・部分を自由に利用することが可能です。 問い合わせ: 本プログラムに関する問い合わせは、改造者(きだ)の下記アドレスま でお願いします。原作者(森氏)には問い合わせをしないようにお願いし ます。 SDI00379@niftyserve.or.jp 謝辞: 本プログラムの原作者の森 公一郎さんには、同プログラムを公開され たこと、そして直接お会いしたときに色々とアドバイスを戴いたことに対 して深く感謝の意を表します。また、乗松保智さんには、本改造のきっか けを作っていただくと同時に、幾つか技術上の質問に答えていただいたこ とを感謝致します。並びに、そもそも我々が LSI C-86 という優秀なコン パイラを気軽に利用できるのは、お二人が所属する LSI Japan 社がそれ を試食版という形で配布することを決められたためであり、同社にも感謝 の意を表するものであります。 * * * インストール方法: コンパイルには、LSI C-86(試食版|製品版)に付属している cpp 用の MAKEFILE をお使い下さい。LSI C-86 の make を使える環境であれば、本 パッケージの CPP.C と LSI C 付属の MAKEFILE を共にカレントディレク トリに置いて、 make と入力するだけで、CPP.EXE ができるはずです。これを LSI C-86 の標 準 CPP.EXE に置き換えてご使用下さい(念のため、古いバージョンの CPP.EXE はリネームするなり、別のディレクトリにバックアップコピーを 取っておくなりすることをお奬めします)。 注意: (1) 本プログラムを、LSI C-86 のオリジナル kmori 版 cpp に代替して 用いる場合には、LSI C-86 の標準ヘッダファイルの一部に修正を加 える必要があります。 assert.h machine.h この二つのファイルの中に、次のマクロ定義があります。 #define _q_(s) # s これを、二つのファイルとも以下のように修正します。 #define _q_(s) _qq_(s) #define _qq_(s) # s 二つのファイルに対して、全く同様に修正してください。空白文字の有 無も同じにして下さい。そうでないと、ANSI の規定にしたがって、cpp がエラーを報告します。 このヘッダの修正は、次のような理由で要求されるものです。 ANSI の規定では『文字列化演算子「#」のオペランドはマクロ展開しな い』という仕様になっています。それにもかかわらず、kmori 版ではこの 展開が行われてしまいます。そうして、LSI C-86 のヘッダファイルの中 では、この _q_ は、引数のマクロ展開が行われることを期待する形で利 用されています。 kmori 版では実際に「#」のオペランドもマクロ展開が行われるために、 このように期待しても問題有りませんでした。ところが改造版では、ANSI の規定通りに「#」のオペランドはマクロ展開を抑制してしまいます。こ のために、元のヘッダの定義では展開結果が期待とは異なってしまうので す。 ・kmori 版におけるオリジナル _q_ の展開のされかた。 1) _q_ の引数をマクロ展開する 2) _q_ 自身を展開する 2-1) 仮引数に実引数のマクロ展開結果を代入 2-2) 代入結果を文字列化 ・ANSI 版におけるオリジナル _q_ の展開のされかた。 1) _q_ 自身を展開する 1-1) 仮引数に実引数を代入 1-2) 代入結果を文字列化 cpp を ANSI 改造版に変更して、標準ヘッダを期待通りに展開するには、 この問題を回避しなければなりません。そこで、_qq_ というマクロを介 在させ、いきなり「#」を適用するのではなく、ワンクッションおくこと で引数のマクロ展開を行うように修正したのです。 ・ANSI 版における修正版 _q_ の展開のされかた。 1) _q_ の引数をマクロ展開する 2) _q_ 自身を展開する 2-1) 仮引数にマクロ展開済みの引数を代入 2-2) _qq_ を呼び出す 3) _qq_ 自身を展開する 3-1) 仮引数に実引数を代入 3-2) 代入結果を文字列化 ここで、_qq_ は引数のマクロ展開をしません。しかし、_q_ が _qq_ を呼び出す時点で、すでに _q_ へ渡された実引数はマクロ展開されてい ます。その結果、修正後の _q_ というマクロは、ANSI に忠実な cpp を 用いても、『引数のマクロ展開を行った後に文字列化する』という働きに なります。これは、修正前の _q_ の定義を kmori 版で用いたものと同じ です。 (2) もしトライグラフを利用したいとお考えであれば、cpp のソースを変 更するか、あるいは、LSI C-86 のコマンドドライバである lcc のソー ス(試食版にも付属しています)を変更しなければなりません。ソー スに変更を加えない限り、オプションの指定をいくら工夫してもトラ イグラフを有効にすることはできません。 以上 /e