CやC++で#define AIUEO 3と定義したとき、ソースコード中のAIUEO_AIUEOまで部分的に置換されて3_3になるのか、疑問に思うことがあります。
結論からいうと、AIUEO_AIUEOは3_3にはなりません。AIUEO_AIUEO全体が1つの識別子として扱われ、マクロ名AIUEOとは別のプリプロセシングトークンだからです。
#defineは一般的な文字列検索・置換とは異なります。この違いを理解すると、変数名の一部にマクロ名が含まれている場合や、マクロで識別子を組み立てたい場合の挙動も分かりやすくなります。
#define AIUEO 3でもAIUEO_AIUEOは置換されない
例えば、次のようなマクロを定義したとします。
#define AIUEO 3
この後にint x = AIUEO;と記述すれば、AIUEOというマクロ名が見つかるため、プリプロセッサによる展開後は概念的にint x = 3;となります。
一方、AIUEO_AIUEOはAIUEO、_、AIUEOという3つの識別子に分割されるわけではありません。AIUEO_AIUEOという1個の識別子です。そのため、#define AIUEO 3の置換対象にはなりません。
なぜアンダースコアで区切られないのか
CやC++では、アンダースコア_は識別子に使用できる文字です。そのため、AIUEO_AIUEOという並びは途中で区切られず、一続きの名前として認識されます。
例えばuser_name、MAX_SIZE、AIUEO_AIUEOなどは、それぞれ1つの識別子です。人間にはアンダースコアが単語の区切りに見えますが、プリプロセッサにとっては「名前の一部」です。
したがって、AIUEOという文字列が識別子の内部に含まれているだけでは、その部分だけをマクロ展開することはありません。
普通の文字列置換とマクロ展開は違う
#defineを「検索した文字を全部置き換える機能」と考えると、AIUEO_AIUEOが3_3になりそうに見えます。しかし、C/C++のプリプロセッサはそのような単純な部分文字列置換をしているわけではありません。
プリプロセッサはソースをプリプロセシングトークンとして扱い、オブジェクト形式マクロではマクロ名と一致する識別子を置換します。そのため、次の例では結果が異なります。
| 記述 | #define AIUEO 3による結果 |
理由 |
|---|---|---|
AIUEO |
3 |
マクロ名と一致 |
AIUEO_AIUEO |
そのまま | 全体で別の識別子 |
AIUEO2 |
そのまま | 別の識別子 |
MY_AIUEO |
そのまま | 別の識別子 |
AIUEO + AIUEO |
3 + 3 |
2個の独立したAIUEO |
つまり、マクロ名と同じ文字列を含むかではなく、マクロ名と一致する独立した識別子になっているかがポイントです。
空白や演算子で分かれていればそれぞれ展開される
アンダースコアは識別子の一部ですが、空白や演算子などによってAIUEOが独立したトークンになればマクロ展開の対象になります。
例えばAIUEO + AIUEOなら、2か所のAIUEOがそれぞれマクロ名として認識されるため、展開すると3 + 3になります。
同様に、int value = AIUEO * 10;ならAIUEOが展開され、概念的にはint value = 3 * 10;となります。重要なのは見た目の文字列ではなくトークンとしての境界です。
「3_3」というトークンを作りたい場合にも注意が必要
仮にAIUEO_AIUEOの両側だけを何らかの方法で3へ置き換えて3_3にしたいと考えても、C/C++の通常の識別子として3_3を使うことはできません。識別子は数字から開始できないためです。
また、C++では数字の区切りとしてアポストロフィを利用する仕組みがありますが、アンダースコアを整数リテラルの単純な桁区切りとして使うものではありません。3_3を「3と3をアンダースコアでつないだ普通の整数」と考えるのも適切ではありません。
「マクロの値を文字列として3_3のように生成したい」「マクロ引数を使って変数名を組み立てたい」という目的なら、単純な#define AIUEO 3とは別のマクロ技法が必要になります。
マクロで名前を連結するときは##演算子を使う
C/C++のプリプロセッサには、マクロの置換リスト内でトークン同士を連結する##演算子があります。これは一般にトークン連結(token pasting)と呼ばれます。
例えば#define JOIN(a,b) a##bのような関数形式マクロを利用すると、マクロ引数として与えたトークンを連結できます。ただし、引数を別のマクロとして先に展開してから連結したい場合には、追加の間接マクロが必要になることがあります。
これは「識別子の途中に含まれる文字列を検索して置換する」機能ではありません。通常のマクロ展開とトークン連結は別の仕組みとして理解しておくことが重要です。
文字列リテラルの中に書いたAIUEOも通常は置換されない
もう一つ混同しやすいのが文字列です。#define AIUEO 3が定義されていても、"AIUEO"という文字列リテラルの中身が自動的に"3"になるわけではありません。
つまり、printf("AIUEO");のような記述では画面に表示する文字列はAIUEOのままです。文字列の内部を一般的な検索・置換のようにマクロが書き換えるわけではないからです。
マクロの値を文字列化したい場合には#を利用する文字列化(stringizing)という別の仕組みがあります。ここでも「#defineは単純なテキスト置換」と覚えてしまうと混乱しやすいため、トークン単位の処理として理解する方が正確です。
実際のコードで確認すると理解しやすい
例えば次のようなイメージです。#define AIUEO 3を定義した後、int AIUEO_AIUEO = AIUEO;と記述したとします。
この場合、左側の変数名AIUEO_AIUEOはそのままで、右辺の独立したAIUEOだけがマクロ展開されます。したがってプリプロセス後の考え方としてはint AIUEO_AIUEO = 3;となります。
この例を見ると、同じ行にAIUEOという文字が複数含まれていても、どこでも機械的に文字列置換されているわけではないことが分かります。
まとめ:AIUEO_AIUEOは1つの識別子なので3_3にはならない
#define AIUEO 3と定義しても、AIUEO_AIUEOは3_3には置換されません。AIUEO_AIUEO全体が1つの識別子であり、AIUEOというマクロ名とは別のトークンとして扱われるためです。
同様にAIUEO2やMY_AIUEOも、その内部にAIUEOという文字列が含まれているだけなので置換されません。一方、AIUEO + AIUEOのようにそれぞれが独立した識別子になっていれば3 + 3へ展開されます。
C/C++の#defineを理解するときは、「文字列の部分一致で検索・置換する」と考えるのではなく、プリプロセッサがトークンを認識し、マクロ名と一致する識別子を展開すると考えるのがポイントです。
また、マクロを使って識別子そのものを組み立てたい場合には##によるトークン連結、値を文字列として扱いたい場合には#による文字列化など、目的に応じた別のプリプロセッサ機能を利用します。


コメント