ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
Oracle Solaris Studio 12.3: C ユーザーガイド Oracle Solaris Studio 12.3 Information Library (日本語) |
以前のバージョンの C でもっとも不明確な仕様は、各ソースファイルを文字の集合から一連のトークンに変換して構文解析できるようにするまでの操作でしょう。具体的には、空白 (コメントも含む) の認識、連続した文字のトークン化、前処理指令行の処理、およびマクロの置換などがあります。しかし、これら操作の優先順位は保証されていませんでした。
ISO C では、このような翻訳段階の順番が指定されています。
ソースファイル内のすべての 3 文字表記シーケンスが置換されます。ISO C にはちょうど 9 つの 3 文字表記シーケンスがありますが、これらは、不完全な文字セットの容認としてのためだけに考案されました。これらは 3 文字から成るシーケンスであり、ISO 646-1983 文字セットにない 1 つの文字を指定します。
表 6-1 3 文字シーケンス
|
これらのシーケンスは ISO C コンパイラによって理解される必要がありますが、推奨されていません。-xtransition オプションを使用するとき、移行モード (-Xt) では、ISO C コンパイラは 3 文字シーケンスを置換するたびに警告します (コメント内でも)。たとえば、次の例を考えてください。
/* comment *??/ /* still comment? */
??/ はバックスラッシュになります。この文字とそれに続く改行は削除されます。結果として、次のようになります。
/* comment */* still comment? */
2 行目の最初の / は、コメントの終わりです。次のトークンは * です。
バックスラッシュと改行文字の組み合わせがすべて削除されます。
ソースファイルが前処理トークンと空白文字のシーケンスに変換されます。各コメントは効果的に空白文字で置換されます。
すべての前処理指令が処理され、すべてのマクロ呼び出しが置換されます。#include でインクルードされた各ソースファイルは、内容が指令行に置換される前の初期段階で実行されます。
すべてのエスケープシーケンス (文字定数と文字列リテラル) が解釈されます。
隣接する文字列リテラルが連結されます。
すべての前処理トークンが通常のトークンに変換されます。コンパイラはこれらを正しく解析し、コードを生成します。
すべての外部オブジェクトと関数参照が解釈処理され、最終的なプログラムになります。
以前の C コンパイラは、そのような単純な一連の段階に従っておらず、それらの手順が適用される順番も予測不可能でした。コンパイラとは別のプリプロセッサが、マクロを置換して指令行を処理するときに、トークンと空白を認識していました。そして、コンパイラがプリプロセッサの出力を適切に再トークン化し、言語を構文解析し、コードを生成していました。
プリプロセッサ内のトークン化プロセスは刻一刻の処理であり、マクロ置換は、トークンベースではなく文字ベースの処理として行われていました。したがって、トークンや空白は前処理中に大きく変動する可能性がありました。
これら 2 つのアプローチからいくつかの違いが生じます。この節の残りでは、マクロ置換中に発生する行の連結、マクロ置換、文字列化、およびトークンの連結によって、コードの動作がどのように変化する可能性があるかを説明します。
K&R C では、バックスラッシュと改行を組み合わせた次の行には、指令、文字列リテラル、文字定数しか指定できませんでした。ANSI/ISO C ではこの概念が拡張され、バックスラッシュと改行の組みの次の行に、あらゆるものを指定できるようになりました。K&R では 1 行は 1 行でしたが ANSIC では複数行組み合わせて 1 行とでき、これが論理行です。したがって、バックスラッシュと改行の組み合わせのいずれかの側でのトークンの別個の認識に依存するコードは、期待どおりに動作しません。
ISO C 以前は、マクロ置換のプロセスについて詳しく記述されていませんでした。この曖昧さにより、極めて多種多様な実装が生み出されました。明白な定数置換や簡単な関数のようなマクロよりも複雑なものに依存するコードは、おそらく正しく移植できませんでした。このマニュアルでは、古い C と ISO C 間のマクロ置換実装の違いをすべて説明することはできません。ほとんどすべてのマクロ置換の結果は、前とまったく同じトークンの連続になります。ただし、ISO C マクロ置換アルゴリズムは、古い C ではできなかったことができます。次の例は、すべての name の使用を name 経由の間接参照で置換することになります。
#define name (*name)
古い C プリプロセッサは数多くの括弧とアスタリスクを生成し、ときには、マクロの再帰についてエラーを生成する場合もあります。
ANSI/ISO C によるマクロ置換方法の主な変更は、マクロ置換演算子 # と ## のオペランド以外のマクロ引数が要求であること、置換トークンリストでの置換前に再帰的に展開することです。ただし、この変更によって、実際に生成されるトークンに差が生じることは滅多にありません。
注 - ISO C では、? でマークされた次の例は、-xtransition オプションを使用するときに、古い機能の使用についての警告を生成します。移行モード (-Xt と -Xs) の場合のみ、結果は以前のバージョンの C と同じになります。
K&R C では、次のコードは文字列リテラル "x y!" を生成していました。
#define str(a) "a!" ? str(x y)
プリプロセッサは、文字列リテラルと文字定数の内部で、マクロパラメータのように見える文字を検索していました。ISO C はこの機能の重要性を認識していましたが、トークンの部分にこの操作を行うことはできませんでした。ISO C では、前述のマクロのすべての呼び出しが、文字列リテラル "a!" を生成します。ISO C で古い効果を実現するには、# マクロ置換演算子と文字列リテラルの連結を使用してください。
#define str(a) #a "!" str(x y)
このコードでは、2 つの文字列リテラル "x y" と "!" が生成され、連結後に同一の "x y!" が生成されます。
文字定数用の操作を完全に代用するものはありません。この機能の主な使用方法は、次の例のようなものでした。
#define CNTL(ch) (037 & ’ch’) ? CNTL(L)
この例では次の結果が生成され、ASCII の Control-L 文字に評価されます。
(037 & ’L’)
最良の解決策は、このマクロのすべての使用を次のように変更することです。
#define CNTL(ch) (037 & (ch)) CNTL(’L’)
このコードの方が読みやすく式にも適用できるため、より使いやすくなっています。
K&R C では、2 つのトークンを連結するために、少なくとも 2 つの方法がありました。次のコード内の両方の呼び出しは、2 つのトークン x と 1 から 1 つの識別子 x1 を生成します。
#define self(a) a #define glue(a,b) a/**/b ? self(x)1 glue(x,1)
ISO C では、どちらの方法も使用できません。ISO C では、どちらの呼び出しも、2 つの別々のトークン x と 1 を生成します。2 つの方法の 2 番目は、## マクロ置換演算子を使用することで、ISO C 用に書き換えることができます。
#define glue(a,b) a ## b glue(x, 1)
# と ## は、__STDC__ が定義されているときだけ、マクロ置換演算子として使用しなければいけません。## は実際の演算子のため、定義と呼び出しの両方で空白について呼び出しをより自由に行うことができます。
コンパイラは、未定義の ## 演算に対して警告の診断を発行するようになりました (C 規格、3.4.3 節)。未定義とは、## を前処理したときの結果に、単一のトークンではなく、複数のトークンが含まれていることを意味します (C 規格、6.10.3.3(3) 節)。未定義の ## 演算の結果は、現在では、## のオペランドを連結することによって作成された文字列をプリプロセスすることによって生成された個別のトークンのうち最初のものと定義されます。
2 つの古い形式の連結方法のうち、最初の方法を再現するための直接的なアプローチは存在しません。しかし、呼び出し時に連結の負荷が発生するため、もう 1 つの方法に比べてあまり使用されせんでした。