C プリプロセッサは、 C、Objective-C、C++、さまざまなFortran言語などのいくつかのコンピュータプログラミング言語用のマクロ プリプロセッサです。プリプロセッサは、ヘッダー ファイルのインクルード、マクロの展開、条件付きコンパイル、行制御を提供します。
プリプロセッサ指令の言語はCの文法とほとんど関係がないため、他の種類のテキストファイルの処理に使用されることもあります。[1]
歴史
このプリプロセッサは、アラン・スナイダーの勧めと、BCPLとPL/Iで利用可能なファイルインクルードメカニズムの有用性への認識により、1973年頃にCに導入されました。そのオリジナルバージョンでは、それぞれパラメータなしのマクロに対して#include、およびを使用したファイルインクルードと単純な文字列置換のみが提供されていました。その後すぐに、最初はマイク・レスク#define、次にジョン・ライザーによって拡張され、引数付きマクロと条件付きコンパイルが組み込まれました。[2]
Cプリプロセッサは、1959年にダグラス・イーストウッドとダグラス・マキロイによって始められたベル研究所の長いマクロ言語の伝統の一部でした。 [3]
フェーズ
前処理は、C 標準で規定されている 変換の最初の 4 つのフェーズ (8 つのフェーズのうち) によって定義されます。
- トライグラフの置換: プリプロセッサはトライグラフのシーケンスを、それが表す文字に置き換えます。このフェーズは、C++17の手順に従ってC23で削除されます。
- 行の結合:エスケープされた 改行シーケンスで継続される物理ソース行は結合されて論理行を形成します。
- トークン化: プリプロセッサは結果を前処理トークンと空白に分割します。コメントは空白に置き換えられます。
- マクロの展開とディレクティブの処理: ファイルのインクルードや条件付きコンパイルなどのプリプロセス ディレクティブ行が実行されます。プリプロセッサはマクロを同時に展開し、C 標準の 1999 バージョン以降では
_Pragma演算子を処理します。
ファイルを含める
プリプロセッサの最も一般的な用途の 1 つは、別のソース ファイルをインクルードすることです。
#include <stdio.h>
int main ( void ) { printf ( "Hello, World! \n " ); return 0 ; }
プリプロセッサは、その行を、関数などを
#include <stdio.h>宣言するファイル「stdio.h」のテキスト コンテンツに置き換えます。printf()
これは二重引用符を使用して記述することもできます (例 ) #include "stdio.h"。ファイル名が山括弧で囲まれている場合、ファイルは標準コンパイラのインクルード パスで検索されます。ファイル名が二重引用符で囲まれている場合、検索パスは現在のソース ファイル ディレクトリを含むように拡張されます。C コンパイラとプログラミング環境にはすべて、プログラマがインクルード ファイルの場所を定義できる機能があります。これは、makefileを使用してパラメーター化できるコマンド ライン フラグを通じて導入できるため、たとえば、異なるオペレーティング システムでは異なるインクルード ファイルのセットを入れ替えることができます。
慣例により、インクルード ファイルの名前には.hまたは.hpp拡張子が付けられます。ただし、この拡張子を遵守する必要はありません。拡張子の付いたファイルは、.def複数回インクルードされるように設計されたファイルであり、そのたびに同じ繰り返しコンテンツが展開されます。は、 XBMイメージ ファイル (同時に C ソース ファイル)
#include "icon.xbm"を参照する可能性があります。
#include#include多くの場合、ガードの使用を強制したり、#pragma once二重包含を防止したりします。
条件付きコンパイル
if-elseディレクティブ#if、、、、、は条件付きコンパイルに使用できます。およびは、およびの#ifdef単純な省略形です。
#ifndef#else#elif#endif#ifdef#ifndef#if defined(...)#if !defined(...)
#if VERBOSE >= 2
printf ( "トレースメッセージ" ); #endif
Microsoft Windowsを対象とするほとんどのコンパイラは、 を暗黙的に定義します_WIN32。[4]これにより、プリプロセッサコマンドを含むコードは、Windowsシステムを対象とする場合にのみコンパイルされます。いくつかのコンパイラは、WIN32代わりに を定義します。 マクロを暗黙的に定義しないコンパイラの場合は_WIN32、 を使用してコンパイラのコマンドラインで指定できます-D_WIN32。
#ifdef __unix__ /* __unix__ は通常、Unix システムをターゲットとするコンパイラによって定義されます */
# include <unistd.h> #elif defined _WIN32 /* _WIN32 は通常、32 ビットまたは 64 ビットの Windows システムをターゲットとするコンパイラによって定義されます */ # include <windows.h> #endif
サンプル コードは、マクロ__unix__が定義されているかどうかをテストします。定義されている場合は、ファイル<unistd.h>がインクルードされます。そうでない場合は、代わりにマクロが定義されているかどうかをテストします_WIN32。定義されている場合は、ファイル<windows.h>がインクルードされます。
より複雑な#if例では演算子を使用できます。例:
#if !(defined __LP64__ || defined __LLP64__) || defined _WIN32 && !defined _WIN64
// 32 ビット システム用にコンパイルしています#else // 64 ビット システム用にコンパイルしています#endif
次のディレクティブを使用すると、翻訳が失敗することもあります#error。
#if RUBY_VERSION == 190
#error 1.9.0 はサポートされていません
#endif
マクロの定義と拡張
マクロには、オブジェクトのようなマクロと関数のようなマクロの 2 種類があります。オブジェクトのようなマクロはパラメータを取りませんが、関数のようなマクロはパラメータを取ります (ただし、パラメータのリストは空になる場合があります)。識別子を各タイプのマクロとして宣言するための一般的な構文は、それぞれ次のとおりです。
#define <識別子> <置換トークンリスト> // オブジェクトのようなマクロ
#define <識別子>(<パラメータリスト>) <置換トークンリスト> // 関数のようなマクロ、パラメータに注意
関数のようなマクロ宣言では、識別子と最初の開き括弧の間に空白文字があってはなりません。空白文字が存在する場合、マクロはオブジェクトのように解釈され、最初の括弧から始まるすべてがトークン リストに追加されます。
マクロ定義は次のように削除できます#undef:
#undef <識別子> // マクロを削除する
識別子がソース コードに現れるたびに、置換トークン リストに置き換えられます。置換トークン リストは空でもかまいません。関数のようなマクロとして宣言された識別子の場合、後続のトークンがマクロ呼び出しの引数リストを開始する左括弧である場合にのみ置き換えられます。引数を持つ関数のようなマクロの展開の正確な手順は微妙です。
オブジェクトのようなマクロは、従来、定数のシンボリック名を作成するための適切なプログラミング手法の一部として使用されていました。たとえば、次のようになります。
#PI 3.14159 を定義する
コード全体で数字をハードコーディングする
代わりに、C と C++ の両方で、特に数字へのポインターが必要な状況では、const修飾子をグローバル変数に適用するという代替手段があります。これにより、値はプリプロセッサによって置換されるのではなく、メモリに格納されます。ただし、最近の C++ コードでは、代わりにC++11constexprで導入されたキーワードが使用されます。
constexpr double PI = 3.14159 ;
として宣言された変数はconstexpr、オブジェクトのようなマクロとして使用され、コンパイル時にその値に置き換えられることがあります。[5]
関数のようなマクロの例は次のとおりです。
#define RADTODEG(x) ((x) * 57.29578)
これは、必要に応じてコードに挿入できるラジアンRADTODEG(34)から度への変換を定義します。たとえば、 です。これはインプレースで展開されるため、定数による繰り返しの乗算はコード全体で表示されません。ここでのマクロは、マクロであり、コンパイルされた関数ではないことを強調するために、すべて大文字で記述されています。
2 番目は、単一の値ではなく式である場合に演算の順序xが誤ってしまう可能性を回避するために、独自の括弧で囲まれています。たとえば、式は;として正しく展開され、括弧なしでは乗算が優先されます。
RADTODEG(r + 1)((r + 1) * 57.29578)(r + 1 * 57.29578)
同様に、外側の括弧のペアは正しい演算順序を維持します。たとえば、は に展開されます。括弧がない場合、 は除算を優先します。
1 / RADTODEG(r)1 / ((r) * 57.29578)1 / (r) * 57.29578
拡張順序
関数のようなマクロの展開は、次の段階で発生します。
- 文字列化操作は、引数の置換リストのテキスト表現に置き換えられます (展開は実行されません)。
- パラメータは、置換リストに置き換えられます (展開は実行されません)。
- 連結演算は、2 つのオペランドの連結結果に置き換えられます (結果のトークンは拡張されません)。
- パラメータから生成されたトークンが展開されます。
- 結果のトークンは通常どおりに展開されます。
これにより、驚くべき結果が生じる可能性があります。
#define HE HI
#define LLO _THERE
#define HELLO "HI THERE"
#define CAT(a,b) a##b
#define XCAT(a,b) CAT(a,b)
#define CALL(fn) fn(HE,LLO)
CAT ( HE , LLO ) // "HI THERE", 連結が通常の展開の前に行われるためXCAT ( HE , LLO ) // HI_THERE", パラメータ ("HE" および "LLO") から発生するトークンが最初に展開されるためCALL ( CAT ) // "HI THERE", これは CAT(a,b) と評価されるため
特別なマクロとディレクティブ
特定のシンボルは、プリプロセス中に実装によって定義される必要があります。これには、プリプロセッサ自体によって事前定義された__FILE__とが含まれ__LINE__、これらは現在のファイルと行番号に展開されます。たとえば、次のようになります。
// デバッグ マクロなので、メッセージの出所を一目で特定できます
// 悪い例
#define WHERESTR "[ファイル %s、行 %d]: "
#define WHEREARG __FILE__、__LINE__
#define DEBUGPRINT2(...) fprintf(stderr、__VA_ARGS__)
#define DEBUGPRINT(_fmt、...) DEBUGPRINT2(WHERESTR _fmt、WHEREARG、__VA_ARGS__)
// または
// 良い例
#define DEBUGPRINT(_fmt、...) fprintf(stderr、"[ファイル %s、行 %d]: " _fmt、__FILE__、__LINE__、__VA_ARGS__)
DEBUGPRINT ( "ねえ、 x=%d \n " 、x );
xは、エラー ストリームにファイルと行番号を先頭に付けて の値を出力します。これにより、メッセージが生成された行にすばやくアクセスできます。WHERESTR引数は、それに続く文字列と連結されることに注意してください。 と の値は__FILE__、ディレクティブ__LINE__を使用して操作できます#line。#lineディレクティブは、以下の行の行番号とファイル名を決定します。例:
#行 314 "pi.c"
printf ( "行=%d ファイル=%s \n " , __LINE__ , __FILE__ );
次の関数を生成しますprintf:
printf ( "行=%d ファイル=%s \n " , 314 , "pi.c" );
ソース コードデバッガーは__FILE__、およびで定義されるソース位置も参照します__LINE__。これにより、まったく異なる言語のコンパイラのターゲット言語として C が使用されている場合に、ソース コードのデバッグが可能になります。最初のC 標準で__STDC__は、実装が ISO 標準に準拠している場合はマクロを1 に定義し、それ以外の場合は 0 に定義し、__STDC_VERSION__実装でサポートされている標準のバージョンを指定する数値リテラルとしてマクロを定義するように指定されていました。標準 C++ コンパイラはマクロをサポートしています__cplusplus。非標準モードで実行されるコンパイラは、これらのマクロを設定してはならず、違いを通知するために他のマクロを定義する必要があります。
その他の標準マクロには__DATE__、現在の日付、__TIME__現在の時刻などがあります。
C 標準の第 2 版であるC99では、 のサポートが追加されました__func__。 には、それが含まれる関数定義の名前が含まれていますが、プリプロセッサは C の文法に依存しないため、関数のローカル変数を使用してコンパイラ自体でこれを行う必要があります。
可変数の引数を取ることができるマクロ (可変長マクロ) は C89 では許可されていませんが、多くのコンパイラによって導入され、C99で標準化されました。可変長マクロは、警告やエラーをログに記録する場合など、可変数のパラメータを取る関数へのラッパーを記述するときに特に便利printfです。
C プリプロセッサのあまり知られていない使用パターンの 1 つに、X マクロがあります。[6] [7] [8] X マクロはヘッダー ファイル.defです。通常、これらは従来の ではなく拡張子を使用します.h。このファイルには、同様のマクロ呼び出しのリストが含まれており、これを「コンポーネント マクロ」と呼ぶことができます。インクルード ファイルは繰り返し参照されます。
多くのコンパイラは追加の非標準マクロを定義しますが、これらのマクロは十分に文書化されていないことがよくあります。これらのマクロの一般的なリファレンスは、定義済み C/C++ コンパイラ マクロ プロジェクトです。このプロジェクトには、「コンパイル時に標準、コンパイラ、オペレーティング システム、ハードウェア アーキテクチャ、さらには基本的なランタイム ライブラリを識別するために使用できるさまざまな定義済みコンパイラ マクロ」がリストされています。
トークンの文字列化
演算子#(文字列化演算子または文字列化演算子とも呼ばれます) は、引用符やバックスラッシュを適切にエスケープして、
トークンを C文字列リテラルに変換します。
例:
#define str(s) #s
str ( p = "foo \n " ;) // 出力は "p = \"foo\\n\";" str ( \ n ) // 出力は "\n"
マクロ引数の展開の文字列化が必要な場合は、2 つのレベルのマクロを使用する必要があります。
#define xstr(s) str(s)
#define str(s) #s
#define foo 4
str ( foo ) // "foo" を出力xstr ( foo ) // "4" を出力
マクロ引数を追加のテキストと組み合わせて文字列化することはできません。ただし、一連の隣接する文字列定数と文字列化された引数を記述することは可能です。その場合、C コンパイラは隣接するすべての文字列定数を 1 つの長い文字列に結合します。
トークンの連結
この##演算子 (「トークン貼り付け演算子」と呼ばれます) は、2 つのトークンを 1 つのトークンに連結します。
例:
#define DECLARE_STRUCT_TYPE(name) typedef struct name##_s name##_t
DECLARE_STRUCT_TYPE ( g_object ); // 出力: typedef struct g_object_s g_object_t;
ユーザー定義のコンパイルエラー
ディレクティブ#errorはエラー ストリームを通じてメッセージを出力します。
#error "エラーメッセージ"
バイナリリソースの包含
C23 では、#embedバイナリ リソースのインクルードに関するディレクティブが導入されます。 [9]これにより、バイナリ ファイル (画像など) を、有効な C ソース ファイル (XBM など) でなくてもプログラムにインクルードできます。また、 や などの外部ツールによる処理を必要とせず、 MSVCで長さ制限がある文字列リテラルxxd -iも使用しません。 ディレクティブと同様に、 は、指定されたリソースのデータに対応するコンマ区切りの整数リストに置き換えられます。より正確には、 型の配列がディレクティブを使用して初期化された場合、結果は、リソースが を使用して配列に書き込まれた場合と同じです(パラメーターによって埋め込み要素の幅が 以外に変更されない限り)。利便性とは別に、 は、as-if ルールにより、ディレクティブを完全な形式に展開する手間が省けるため、コンパイラにとっても扱いやすくなります。
xxd -iunsigned char#embedfreadCHAR_BIT#embed
埋め込まれるファイルは、 と同じ方法#include、つまりシェブロンまたは引用符で囲んで指定できます。 ディレクティブでは、ファイル名に続く特定のパラメータをディレクティブに渡して動作をカスタマイズすることもできます。 C 標準では次のパラメータが定義されており、実装では独自のパラメータを定義できます。 パラメータは、limit組み込まれるデータの幅を制限するために使用されます。これは主に、urandomなどの「無限」ファイルで使用することを目的としています。prefixおよびsuffixパラメータを使用すると、埋め込まれたデータのプレフィックスとサフィックスをプログラマが指定できます。これは、埋め込まれたリソースが空でない場合にのみ使用されます。最後に、if_emptyリソースが空の場合 (ファイルが空の場合、または制限に 0 が指定された場合)、 パラメータがディレクティブ全体を置き換えます。 すべての標準パラメータは、C23 の標準属性と同様に、二重のアンダースコアで囲むこともできます。たとえば、__prefix__は と互換性があります。実装定義のパラメータは、属性prefix構文に似た形式(例: ) を使用しますが、角括弧は使用しません。すべての標準パラメータには引数を渡す必要がありますが (たとえば、limit には幅が必要です)、これは通常オプションであり、引数が不要な場合は括弧のセットも省略できます。これは、実装定義のパラメータの一部に当てはまる場合があります。
vendor::attr
実装
すべての C、C++、および Objective-C 実装ではプリプロセッサが提供されます。プリプロセスはこれらの言語の必須ステップであり、その動作は ISO C 標準などのこれらの言語の公式標準によって記述されているためです。
実装は独自の拡張や逸脱を提供する場合があり、標準への準拠度合いも異なります。その正確な動作は、呼び出し時に提供されるコマンドラインフラグに依存する場合があります。たとえば、GNU Cプリプロセッサは、特定のフラグを提供することで、より標準に準拠することができます。[10]
コンパイラ固有のプリプロセッサ機能
ディレクティブはコンパイラ固有のディレクティブ#pragmaであり、コンパイラベンダーが独自の目的で使用できます。たとえば、は特定のエラー メッセージを抑制したり、ヒープとスタックのデバッグを管理したりするためによく使用されます。OpenMP 並列化ライブラリをサポートするコンパイラは、を使用してループを自動的に並列化できます。
#pragmafor#pragma omp parallel for
#pragmaC99 では、浮動小数点の実装を制御するために使用される、という形式のいくつかの標準ディレクティブが導入されました#pragma STDC ...。代替のマクロのような形式_Pragma(...)も追加されました。
- 多くの実装では、トライグラフをサポートしていないか、デフォルトでは置き換えません。
- 多くの実装(GNU、Intel、Microsoft、IBMのCコンパイラなど)では、出力に警告メッセージを表示するが、コンパイルプロセスを停止しない非標準のディレクティブを提供しています(C23 [11]とC++23 [12]はこの目的のために標準に追加されます)。典型的な使用法は、現在では非推奨で
#warning互換性の理由からのみ含まれている古いコードの使用について警告することです。たとえば、// GNU、Intel、IBM #warning "ABC は非推奨なので使用しないでください。代わりに XYZ を使用してください。"
// Microsoft #pragma message("ABC は非推奨なので使用しないでください。代わりに XYZ を使用してください。")
- 一部のUnixプリプロセッサは伝統的に「アサーション」を提供してきましたが、これはプログラミングで使用されるアサーションとはほとんど類似点がありません。[13]
- GCCは
#include_next同じ名前のヘッダーを連鎖させる機能を備えている。[14]
言語固有のプリプロセッサ機能
いくつかの言語の仕様によって C プリプロセッサに追加され、その言語に固有のプリプロセッサ ディレクティブがいくつかあります。
- Objective-Cプリプロセッサには があり
#import、これは に似ています#includeが、ファイルを 1 回だけインクルードします。C で同様の機能を持つ一般的なベンダー プラグマは です#pragma once。 - C++20以降のC++には、モジュール用のimportディレクティブとmoduleディレクティブがあります。[15] [16]これらのディレクティブは文字で始まらない唯一のディレクティブです。代わりに、それぞれとで
#始まり、オプションでが前に付きます。importmoduleexport
その他の用途
C プリプロセッサは、付属のコンパイラとは別に呼び出すことができるため、異なる言語で個別に使用できます。注目すべき例としては、現在では非推奨となっているimakeシステムでの使用や、Fortran の前処理などがあります。ただし、汎用プリプロセッサとしての使用には制限があり、入力言語は十分に C に類似している必要があります。[10] GNU Fortranコンパイラは、特定のファイル拡張子が使用されている場合、Fortran コードをコンパイルする前に自動的に「従来モード」(以下を参照) の cpp を呼び出します。[17] Intel は、同様の機能を持つifortコンパイラで使用するための Fortran プリプロセッサ fpp を提供しています。[18]
CPP は、ほとんどのアセンブリ言語や Algol のような言語でも問題なく動作します。このためには、言語構文が CPP 構文と競合しないことが求められます。つまり、 で始まる行はなく、cpp が文字列リテラル#として解釈して無視する二重引用符は、それ以外の構文上の意味を持ちません。「従来のモード」(ISO 以前の C プリプロセッサのように動作する) は、一般的に、より許容度が高く、このような用途に適しています。[19]
C プリプロセッサはチューリング完全ではありませんが、非常に近いものです。再帰計算を指定できますが、実行される再帰の量の上限は固定されています。[20]しかし、C プリプロセッサは汎用プログラミング言語として設計されておらず、汎用プログラミング言語として優れたパフォーマンスを発揮しません。C プリプロセッサには、再帰マクロ、引用符による選択的展開、条件文での文字列評価など、他のプリプロセッサの機能がないため、m4などのより一般的なマクロプロセッサと比較すると非常に制限されています。
参照
参考文献
- ^ Cプリプロセッサによる汎用テキスト前処理。JavaScriptを搭載
- ^ リッチー(1993)
- ^ 「Bell SAP – 条件付きおよび再帰マクロを備えた SAP」。HOPL : プログラミング言語のオンライン歴史百科事典。
- ^ 定義済みの ANSI C および Microsoft C++ 実装マクロのリスト。
- ^ Gabriel Dos Reis、Bjarne Stroustrup (2010年3月22日). 「システムプログラミング言語の一般的な定数式、SAC '10会議録」(PDF) 。 2018年6月13日時点のオリジナルよりアーカイブ(PDF) 。 2024年7月8日閲覧。
- ^ Wirzenius, Lars. C「類似のデータ型を実装するためのプリプロセッサトリック」。2011 年 1 月 9 日閲覧
- ^ マイヤーズ、ランディ (2001 年 5 月)。「新しい C: X マクロ」。ドクター・ドブのジャーナル。2008 年5 月 1 日閲覧。
- ^ Beal, Stephan (2004 年 8 月). 「Supermacros」 . 2008 年10 月 27 日閲覧。
{{cite journal}}:ジャーナルを引用するには|journal=(ヘルプ)が必要です - ^ 「WG14-N3017 : #embed – スキャン可能でツールフレンドリーなバイナリリソースインクルードメカニズム」。open-std.org。2022年6月27日。2022年12月24日時点のオリジナルよりアーカイブ。
- ^ ab 「C プリプロセッサ: 概要」 。2016年7 月 17 日閲覧。
- ^ 「WG14-N3096 : ISO/IEC 9899:2023 のドラフト」(PDF) . open-std.org . 2023年4月1日。2023年4月2日時点のオリジナルよりアーカイブ(PDF) 。
- ^ 「プログラミング言語C++の標準作業草案」(PDF) 2023年3月22日。
- ^ GCC 廃止された機能
- ^ 「ラッパー ヘッダー (C プリプロセッサ)」。
- ^ 「N4720: ワーキングドラフト、モジュールの C++ 拡張」(PDF) 。2019年 4 月 30 日時点のオリジナルよりアーカイブ(PDF) 。
- ^ 「P1857R1 – モジュール依存関係の検出」。
- ^ 「1.3 前処理と条件付きコンパイル」。GNU プロジェクト。
- ^ 「fpp プリプロセッサの使用」。Intel 。2015 年10 月 14 日閲覧。
- ^ 「概要 (C プリプロセッサ)」。gcc.gnu.org。
そうは言っても、C 以外の言語で cpp を使用しても問題ない場合がよくあります。他の Algol 風プログラミング言語は多くの場合安全です (Ada など)。アセンブリも同様ですが、注意が必要です。-traditional-cpp モードでは、より多くの空白が保持され、その他の点ではより寛容です。多くの問題は、ネイティブ言語のコメントではなく C または C++ スタイルのコメントを記述し、マクロをシンプルにすることで回避できます。
- ^ 「C99 プリプロセッサはチューリング完全か?」。2016 年 4 月 24 日時点のオリジナルよりアーカイブ。
出典
- Ritchie, Dennis M. (1993年 3 月)。「C 言語の開発」。ACM SIGPLAN 通知。28 ( 3 )。ACM : 201–208。doi : 10.1145/155360.155580。
- リッチー、デニスM. (1993)。「C 言語の発展」。プログラミング言語の歴史に関する第 2 回 ACM SIGPLAN 会議 (HOPL-II) 。ACM。pp . 201–208。doi : 10.1145 /154766.155580。ISBN 0-89791-570-4. 2014年11月4日閲覧。
外部リンク
- ISO/IEC 9899。C11 標準の最新の公開バージョンは最終ドラフトです。
- GNU CPP オンラインマニュアル
- Visual Studio .NET プリプロセッサ リファレンス
- 定義済み C/C++ コンパイラ マクロ プロジェクト: 「コンパイル時に標準、コンパイラ、オペレーティング システム、ハードウェア アーキテクチャ、さらには基本的なランタイム ライブラリを識別するために使用できるさまざまな定義済みコンパイラ マクロ」をリストします。
