CやC++のプリプロセッサについて、「#define AIUEO 3 と定義したら AIUEO_AIUEO は 3_3 に置換されるのか」という例を見て、「そもそも3_3とは何なのか」「3_3()という関数があるのか」「数値リテラルなのか」と疑問に思うことがあります。
結論からいうと、この例で重要なのは3_3というコードを実際に使いたいわけではなく、#defineによるマクロ置換が文字列の部分一致で行われるのかを説明するための仮定です。そして通常のオブジェクト形式マクロでは、AIUEO_AIUEOは3_3にはなりません。
AIUEO_AIUEO全体が一つの識別子として扱われるため、AIUEOという別のトークンに対するマクロ定義は適用されないからです。ここを理解すると、C/C++の#defineが単純な文字列検索・置換ではないことが分かります。
「#define AIUEO 3」は何をしているのか
C/C++で#define AIUEO 3と記述すると、プリプロセッサに対してAIUEOというマクロを3へ置き換えるよう定義したことになります。
例えば次のようなコードを考えます。
#define AIUEO 3
int x = AIUEO;
プリプロセッサによるマクロ展開後は、概念的にはint x = 3;となります。この場合の3は整数リテラルとしてC/C++のコード中で意味を持ちます。
AIUEO_AIUEOは3_3にはならない
では、同じマクロ定義がある状態でAIUEO_AIUEOと書いた場合はどうなるのでしょうか。
AIUEO_AIUEOは3_3には置換されず、基本的にAIUEO_AIUEOのままです。理由は、プリプロセッサが「AIUEOという文字を見つけたら、その部分を片っ端から3に変える」という単純な文字列置換をしているわけではないからです。
AIUEO_AIUEOは一つの識別子・プリプロセッシングトークンです。その内部に文字列としてAIUEOが2回含まれていても、AIUEOという独立したトークンが存在するわけではありません。
アンダースコア「_」は識別子の一部になれる
ここで混乱しやすいポイントがアンダースコアです。「AIUEO」「_」「AIUEO」という3つに分割されそうに見えますが、C/C++ではアンダースコアは識別子を構成できる文字です。
そのため、AIUEO_AIUEOは全体で一つの識別子として認識されます。例えば次のような変数名を書くこともできます。
int user_count = 10;
int player_score = 100;
int AIUEO_AIUEO = 5;
user_countがuserとcountに勝手に分割されないのと同じように、AIUEO_AIUEOも途中にあるAIUEOだけをマクロとして置換されません。
では「3_3」という表現は何のために出てきたのか
「AIUEO_AIUEOは3_3に置換されますか?」という質問に登場する3_3は、通常、何か特別な関数や値を意味するために書かれているわけではありません。
質問者が「もし#defineが単純な部分文字列置換なら、AIUEO_AIUEOの2つのAIUEOがそれぞれ3へ変わって3_3になるのでは?」と考え、その予想結果を表現しているだけです。
つまり、ここでの3_3はマクロ展開の仕組みを確認するために提示された仮想的な結果と考えると理解しやすいでしょう。「3_3という機能を使いたい」という話ではありません。
「3_3()という関数がある」という意味でもない
3_3を見て「では3_3()という関数を呼び出すのか」と考える必要もありません。そもそも通常のC/C++では、一般的な識別子を数字から始めることはできません。
例えばAIUEO_AIUEO()という関数名なら、識別子として成立します。しかし、それが仮に単純な文字列置換によって3_3()になったとしても、「3_3という関数があらかじめ存在する」という意味にはなりません。
さらに重要なのは、実際には前述の理由からAIUEO_AIUEOの内部にあるAIUEOは置換されないため、そもそも通常のマクロ展開によって3_3()になるという前提自体が成立しないことです。
AIUEOを独立させて書けば置換される
比較するとマクロ置換のルールが分かりやすくなります。例えば次のコードです。
#define AIUEO 3
int a = AIUEO + AIUEO;
この場合はAIUEOがそれぞれ独立したトークンなので、両方が展開され、概念的にはint a = 3 + 3;となります。
一方、int AIUEO_AIUEO = 10;ならAIUEO_AIUEO全体が一つのトークンなので変化しません。この違いが元の疑問の核心です。
空白を入れれば結果は変わる
例えばAIUEO _ AIUEOのように空白を入れれば、字句上の扱いはAIUEO_AIUEOとは異なります。前者ではAIUEOが独立したトークンとして存在するため、マクロ展開の対象になり得ます。
ただし、展開後に得られたトークン列がC/C++の文法として有効なコードになるかどうかは別問題です。プリプロセッサによってマクロを展開できることと、展開後のコードがコンパイル可能であることは同じではありません。
つまり#defineを理解するときは、「最終的にどんなプログラムになるか」だけでなく、「プリプロセッサがどのトークンをマクロ名として認識するか」を考えることが大切です。
文字列の中にAIUEOがあっても通常は置換されない
同じ理由から、文字列リテラルの中にマクロ名と同じ文字が含まれていても、通常のマクロ展開によってその中身が書き換えられるわけではありません。
例えば#define AIUEO 3があっても、printf("AIUEO");の文字列内にあるAIUEOが勝手に3になってprintf("3");になるわけではありません。
「ソースコード上でAIUEOという5文字を発見したら全部3へ置換する」と考えると、このような部分で誤解が生じます。一般的なテキストエディタの検索・置換とC/C++プリプロセッサのマクロ展開は別物です。
似た名前のマクロも別物として扱われる
例えばAIUEOを定義したからといって、AIUEO2、MY_AIUEO、AIUEO_TESTまで部分的に置換されるわけではありません。それぞれ別の識別子です。
具体的には#define AIUEO 3がある場合でも、AIUEO2は32にはなりませんし、MY_AIUEOがMY_3になるわけでもありません。
この性質があるからこそ、マクロ名と一部の文字が一致する変数名や関数名まで意図せず破壊されることを避けられます。
マクロで名前を組み立てる特殊な仕組みは存在する
ここまで説明した通常のマクロ置換とは別に、C/C++のプリプロセッサにはトークンを結合する##という演算子があります。これは関数形式マクロなどで識別子を組み立てる際に使われます。
例えば概念を示す簡単な例として、#define JOIN(a,b) a##bのようなマクロを利用すると、引数として渡したトークン同士を結合できます。
ただし、これは#define AIUEO 3と書いただけでAIUEO_AIUEOの一部分が置換されるという話とは別です。元の質問を理解する段階では、「通常の#defineは識別子内部の部分文字列を置換しない」と覚えておけば十分です。
3_3は整数リテラルなのか
ここはCとC++のバージョンによる違いもあるため、「3_3は必ず整数リテラルである」と一括して説明するのは正確ではありません。少なくとも元の質問における3_3は、リテラルを使う目的で提示されたものではありません。
近年のC++には、数値を読みやすく区切るための桁区切りとしてアポストロフィを使う構文があります。例えば1'000'000のように記述できますが、これはアンダースコアを使った1_000_000とは別です。
したがって、元の「3_3」という表記についてリテラルや関数としての意味を探すよりも、「質問者が部分置換された場合の予想結果として3_3と書いただけ」と理解するのが本筋です。
元の質問を自然な日本語に言い換えるとどうなる?
「#define AIUEO 3 と書いた場合、AIUEO_AIUEOは3_3に置換されますか?それとも違う単語として認識されますか?」という質問は、より平易にすると次のような意味です。
「C/C++の#defineは、変数名などの途中にマクロ名と同じ文字が入っていた場合、その部分まで置き換えるのでしょうか? それともAIUEOとAIUEO_AIUEOは別々の名前として扱われるのでしょうか?」
答えは後者です。AIUEOとAIUEO_AIUEOは別のトークンなので、#define AIUEO 3だけではAIUEO_AIUEOは変更されません。
まとめ:3_3に特別な意味があるのではなく、マクロの部分置換を確認する例
#define AIUEO 3という定義があれば、独立したAIUEOというマクロは3へ展開されます。しかし、AIUEO_AIUEOは全体で一つの識別子として扱われるため、その中のAIUEOだけが取り出されて3_3になるわけではありません。
したがって、元の質問に出てくる3_3について「3_3()という関数が存在するのか」「3_3という値を使いたいのか」と考える必要はありません。単に「もしAIUEOという文字列が部分的に全部3へ置換されたら、結果は3_3になるのでは?」という仮定を表したものです。
C/C++の#defineを理解する際のポイントは、単純な文字列の検索置換ではなく、プリプロセッシングトークンを単位としてマクロ展開が行われることです。AIUEO、AIUEO_AIUEO、AIUEO2はそれぞれ別のトークンであり、AIUEOを定義しただけでは残りの名前の一部分まで置換されません。


コメント