Oracle Solaris Studio 12.2: C ユーザーガイド

6.5 トークン化と前処理

以前のバージョンの C でもっとも不明確な仕様は、各ソースファイルを文字の集合から一連のトークンに変換して構文解析できるようにするまでの操作でしょう。具体的には、空白 (コメントも含む) の認識、連続した文字のトークン化、前処理指令行の処理、およびマクロの置換などがあります。しかし、これら操作の優先順位は保証されていませんでした。

6.5.1 ISO C の翻訳段階

ISO C では、このような翻訳段階の順番が指定されています。

ソースファイル内のすべての 3 文字表記シーケンスが置換されます。ANSI/ISO C は、9 つの 3 文字表記シーケンスを持っています。これらのシーケンスはもともと文字セットの不完全な点を補うために導入されました。しかし、現在では、この 3 文字シーケンスは ISO 646-1983 文字セットに含まれない文字を指定するために使用されています。

表 6–1 3 文字シーケンス

3 文字シーケンス  

変換後の文字  

??=

#

??-

~

??(

[

??)

]

??!

|

??<

{

??>

}

??/

\

??’

^

ISO C コンパイラは前述のシーケンスを理解するはずです。しかし、これらのシーケンスを使用することはお勧めしません。-xtransition オプションを使用したとき、移行モード (-Xt) では、ISO C コンパイラは 3 文字シーケンスを置換するたびに警告を発行します (コメント内でも)。たとえば、次の例を考えてください。


/* comment *??/
/* still comment? */

??/ はバックスラッシュになります。この文字とそれに続く改行は削除されます。結果として、次のようになります。


/* comment */* still comment? */

2 行目の最初の / は、コメントの終わりです。次のトークンは * です。

  1. バックスラッシュと改行文字の組み合わせがすべて削除されます。

  2. ソースファイルが前処理トークンと空白文字のシーケンスに変換されます。各コメントは必要最低限の空白文字で置換されます。

  3. すべての前処理指令が処理され、すべてのマクロ呼び出しが置換されます。#include でインクルードされた各ソースファイルは、内容が指令行に置換される前の初期段階で実行されます。

  4. すべてのエスケープシーケンス (文字定数と文字列リテラル) が解釈されます。

  5. 隣接する文字列リテラルが連結されます。

  6. すべての前処理トークンが通常のトークンに変換されます。コンパイラはこれらのトークンを適切に構文解析して、コードを生成します。

  7. すべての外部オブジェクトと関数参照が解釈処理され、最終的なプログラムになります。

6.5.2 古い C の翻訳段階

以前の C コンパイラは、このような単純な順番に従いませんでした。また、これらの段階がいつ適用されるかも保証されていませんでした。コンパイラとは別のプリプロセッサが、マクロを置換して指令行を処理するときに、トークンと空白を認識していました。そして、コンパイラがプリプロセッサの出力を適切に再トークン化し、言語を構文解析し、コードを生成していました。

プリプロセッサ内のトークン化処理は必要に応じて行われる操作で、マクロ置換は (トークンベースではなく) 文字ベースの操作として行われます。したがって、前処理中にトークンと空白は大きく変動する可能性がありました。

2 つの方法の間には、いくつか異なる点があります。この節の後半では、マクロ置換中に発生する行の連結、マクロ置換、文字列化、およびトークンの連結によって、コードの動作がどのように変化するかを説明します。

6.5.3 論理的なソース行

K&R C では、バックスラッシュと改行を組み合わせた次の行には、指令、文字列リテラル、文字定数しか指定できませんでした。ANSI/ISO C ではこの概念が拡張され、バックスラッシュと改行の組みの次の行に、あらゆるものを指定できるようになりました。K&R では 1 行は 1 行でしたが ANSIC では複数行組み合わせて 1 行とでき、これが論理行です。したがって、バックスラッシュと改行の組み合わせのどちら側にあるかによってトークンの認識が異なるコードは、期待どおりに動作しません。

6.5.4 マクロ置換

ISO C 以前には、マクロ置換処理については詳細に定義されていません。この曖昧さのために、処理系に多くの差が生まれました。したがって、明白な定数置換や簡単な関数のようなマクロよりも複雑なものを持つコードは、おそらく完全には移植できません。このマニュアルでは、古い C と ISO C 間のマクロ置換実装の違いをすべて説明することはできません。ほとんどすべてのマクロ置換の結果は、前とまったく同じトークンの連続になります。ただし、ISO C マクロ置換アルゴリズムは、古い C ではできなかったことができます。たとえば、次を見てください。


#define name (*name)

この例は、すべての namename 経由の間接参照で置換します。古い C プリプロセッサは数多くの括弧とアスタリスクを生成し、ときには、マクロの再帰についてエラーを生成する場合もあります。

ANSI/ISO C によるマクロ置換方法の主な変更は、マクロ置換演算子 ### のオペランド以外のマクロ引数が要求であること、置換トークンリストでの置換前に再帰的に展開することです。ただし、この変更によって、実際に生成されるトークンに差が生じることは滅多にありません。

6.5.5 文字列の使用


注 –

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)

これは、次を生成します。


(037 & ’L’)

これは、ASCII の Control-L 文字と同じです。最良の解決策は、このマクロを次のように変更することです。


#define CNTL(ch) (037 & (ch))
CNTL(’L’)

このコードの方が読みやすく式にも適用できるため、より使いやすくなっています。

6.5.6 トークンの連結

K&R C では、2 つのトークンを連結するために、少なくとも 2 つの方法がありました。次の 2 つの呼び出しは、2 つのトークン x1 から 1 つの識別子 x1 を生成します。


#define self(a) a
#define glue(a,b) a/**/b ?
self(x)1
glue(x,1)

ISO C では、どちらの方法も使用できません。ISO C では、前述の呼び出しは、両方とも 2 つの別々のトークン x1 を生成します。しかし、前述の呼び出しの内 2 番目の方法については、## マクロ置換演算子を使用すれば、ISO C 用に書き換えることができます。


#define glue(a,b) a ## b
glue(x, 1)

# と ## は、__STDC__ が定義されているときだけ、マクロ置換演算子として使用しなければいけません。## は実際の演算子のため、定義と呼び出しの両方で空白をより自由に使うことができます。

コンパイラは、未定義の ## 演算に対して警告の診断を発行するようになりました (C 規格、3.4.3 節)。未定義とは、## を前処理したときの結果に、単一のトークンではなく、複数のトークンが含まれていることを意味します (C 規格、6.10.3.3(3) 節)。未定義の ## 演算の結果は、現在では、## のオペランドを連結することによって作成された文字列をプリプロセスすることによって生成された個別のトークンのうち最初のものと定義されます。

前述の古い形式の連結方法のうち、最初の方法を直接代用できる方法はありません。しかし、この方法では呼び出し時に連結の処理が必要なため、ほかの方法に比べてあまり使用されることはありませんでした