知識ベース

Cプリプロセッサ

Cプリプロセッサまたはcppは、CおよびC ++コンピュータプログラミング言語用のマクロプリプロセッサです。プリプロセッサは、ヘッダーファイル、マクロ展開、条件付きコンパイル、および行制御を含める機能を提供します。

多くのC実装では、翻訳の最初の部分としてコンパイラーによって呼び出される別個のプログラムです。

プリプロセッサディレクティブの言語は、Cの文法にわずかに関連しているだけであるため、他の種類のテキストファイルの処理に使用されることがあります。

段階

前処理は、C標準で指定された最初の4つの(8つの) 変換フェーズによって定義されます。

  1. トライグラフ置換:プリプロセッサは、トライグラフシーケンスを、それらが表す文字で置換します。
  2. 行のスプライシング:エスケープされた改行シーケンスで継続される物理ソース行は、論理行を形成するためにスプライスされます。
  3. トークン化:プリプロセッサは、結果を前処理トークンと空白に分割します。コメントを空白に置き換えます。
  4. マクロ展開とディレクティブ処理:ファイルのインクルードと条件付きコンパイルを含むディレクティブ行の前処理が実行されます。プリプロセッサはマクロを同時に展開し、1999年版のC標準では、_Pragma演算子を処理します。

ファイルを含める

プリプロセッサの最も一般的な用途の1つは、別のファイルを含めることです。

#include stdio.h> int main(void){printf( "Hello、world!\ n"); 0を返します。 }

プリプロセッサは、#include stdio.h>の行をファイル 'stdio.h'のテキストに置き換えます。これは、特にprintf()関数を宣言します。

#include "stdio.h"のように、二重引用符を使用して記述することもできます。ファイル名が山かっこで囲まれている場合、ファイルは標準コンパイラインクルードパスで検索されます。ファイル名が二重引用符で囲まれている場合、検索パスは現在のソースディレクトリを含むように展開されます。 Cコンパイラとプログラミング環境にはすべて、インクルードファイルの場所をプログラマが定義できる機能があります。これは、たとえば、異なるオペレーティングシステム用に別のインクルードファイルのセットをスワップできるように、メイクファイルを使用してパラメーター化できるコマンドラインフラグを介して導入できます。

慣例により、インクルードファイルには.h拡張子が付き、他のユーザーがインクルードしないファイルには.c拡張子が付きます。ただし、これを遵守する必要はありません。拡張子が.defのファイルは、同じ繰り返しコンテンツを展開するたびに複数回含めるように設計されたファイルを示す場合があります。 #include "icon.xbm"は、XBMイメージファイル(Cソースファイルでもある)を参照する可能性があります。

#includeでは、#includeガードまたは#pragmaの使用を1度強制して、二重に含まれないようにします。

条件付きコンパイル

if-elseディレクティブ#if、#ifdef、#ifndef、#else、#elif、および#endifは、条件付きコンパイルに使用できます。

#if VERBOSE> = 2 printf( "trace message"); #endif

Microsoft Windowsを対象とするほとんどのコンパイラは、_WIN32を暗黙的に定義します。これにより、プリプロセッサコマンドを含むコードは、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!(定義済み__LP64__ ||定義済み__LLP64__)|| defined _WIN32 &&!defined _WIN64 // 32ビットシステム用にコンパイルしています#else // 64ビットシステム用にコンパイルしています#endif

#errorディレクティブを使用して、翻訳が失敗することもあります。

#if RUBY_VERSION == 190#エラー1.9.0は​​サポートされていません#endif

マクロの定義と展開

マクロには、 オブジェクトのようなもの関数のよう ものの2種類があります。オブジェクトのようなマクロはパラメーターを取りません。関数のようなマクロは行います(ただし、パラメーターのリストは空でもかまいません)。各タイプのマクロとして識別子を宣言するための一般的な構文は、それぞれ次のとおりです。

#define identifier> replacement token list> //オブジェクトのようなマクロ#define identifier>(parameter list>)replacement token list> //関数のようなマクロ、ノートパラメーター

関数のようなマクロ宣言では、識別子と最初の開き括弧の間に空白があってはなりません。空白が存在する場合、マクロはオブジェクトのように解釈され、すべてがトークンリストに追加された最初の括弧から始まります。

マクロ定義は#undefで削除できます:

#undef identifier> //マクロを削除します

ソースコードに識別子が表示されるたびに、空の場合もある置換トークンリストに置き換えられます。関数のようなマクロであると宣言された識別子の場合、次のトークンがマクロ呼び出しの引数リストを開始する左括弧でもある場合にのみ置き換えられます。引数付きの関数のようなマクロを展開するための正確な手順は微妙です。

オブジェクトのようなマクロは、従来から、定数のシンボル名を作成するための優れたプログラミング手法の一部として使用されていました。

#define PI 3.14159

コード全体で数字をハードコーディングする代わりに。 CとC ++の両方の代替手段、特に数値へのポインターが必要な状況では、const修飾子をグローバル変数に適用します。これにより、値はプリプロセッサによって置き換えられるのではなく、メモリに保存されます。

関数のようなマクロの例は次のとおりです。

#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が除算を優先します。

拡張の順序

関数のようなマクロ展開は、次の段階で発生します。

  1. 文字列化操作は、引数の置換リストのテキスト表現に置き換えられます(展開は実行されません)。
  2. パラメータは、置換リストに置き換えられます(展開は実行されません)。
  3. 連結操作は、2つのオペランドの連結結果に置き換えられます(結果のトークンを展開することはありません)。
  4. パラメータから生成されたトークンは展開されます。
  5. 結果のトークンは通常どおりに展開されます。

これにより、驚くべき結果が得られる場合があります。

#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」)からのトークンが最初に展開されるため(CAT)//パラメーターが最初に展開されるため、「HI THERE」

特別なマクロとディレクティブ

特定のシンボルは、前処理中に実装で定義する必要があります。これらには、プリプロセッサによって事前定義された__FILE__および__LINE__が含まれ、現在のファイルと行番号に展開されます。たとえば、次のとおりです。

//マクロをデバッグして一目でメッセージの発信元を特定できる//悪い#define WHERESTR ":" #define WHEREARG __FILE__、__LINE__ #define DEBUGPRINT2(...)fprintf(stderr、__VA_ARGS__)#define DEBUGPRINT(_fmt、 ...)DEBUGPRINT2(WHERESTR _fmt、WHEREARG、__VA_ARGS__)//または//良い#define DEBUGPRINT(_fmt、...)fprintf(stderr、 ":" _fmt、__FILE__、__LINE__、__VA_ARGS__)DEBUGPRINT( "hey、 x =%d \ n "、x);

ファイルと行番号が前に付いたxの値をエラーストリームに出力し、メッセージが生成された行にすばやくアクセスできるようにします。 WHERESTR引数はそれに続く文字列と連結されることに注意してください。 __FILE__および__LINE__の値は、#lineディレクティブで操作できます。 #lineディレクティブは、下の行の行番号とファイル名を決定します。例えば:

#line 314 "pi.c" printf( "line =%d file =%s \ n"、__LINE__、__FILE__);

printf関数を生成します。

printf( "line =%d file =%s \ n"、314、 "pi.c");

ソースコードデバッガーは、__ FILE__および__LINE__で定義されたソース位置も参照します。これにより、Cがコンパイラのターゲット言語として使用されている場合、まったく異なる言語のソースコードのデバッグが可能になります。最初のC規格では、実装がISO規格に準拠している場合はマクロ__STDC__を1に定義し、そうでない場合は0を定義し、マクロ__STDC_VERSION__は実装がサポートする規格のバージョンを指定する数値リテラルとして定義することを指定しました。標準C ++コンパイラは__cplusplusマクロをサポートします。非標準モードで実行しているコンパイラは、これらのマクロを設定したり、違いを知らせる他のマクロを定義したりしてはなりません。

他の標準マクロには、現在の日付__DATE__と現在の時刻__TIME__が含まれます。

C標準の第2版であるC99では、__ func__のサポートが追加されました。これには、含まれる関数定義の名前が含まれますが、プリプロセッサはCの文法にとらわれないため、これを使用してコンパイラ自体で行う必要があります関数にローカルな変数。

さまざまな数の引数を取ることができるマクロ(可変アドマクロ)はC89では許可されていませんが、多くのコンパイラーによって導入され、C99で標準化されました。可変長マクロは、printfなどの可変数のパラメーターを取る関数にラッパーを書き込む場合、たとえば警告やエラーを記録する場合に特に便利です。

Cプリプロセッサのあまり知られていない使用パターンの1つは、X-Macrosとして知られています。 X-Macroはヘッダーファイルです。通常、これらは従来の「.h」の代わりに拡張子「.def」を使用します。このファイルには、「コンポーネントマクロ」と呼ばれる同様のマクロ呼び出しのリストが含まれています。その後、インクルードファイルは繰り返し参照されます。

多くのコンパイラは、追加の非標準マクロを定義していますが、これらは多くの場合文書化が不十分です。これらのマクロの一般的なリファレンスは、定義済み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「エラーメッセージ」

実装

C、C ++、Objective-Cの実装はすべて、プリプロセッサを提供します。プリプロセッシングはこれらの言語に必要なステップであり、その動作はISO C標準など、これらの言語の公式標準によって記述されています

実装は独自の拡張と逸脱を提供し、文書化された標準への準拠の程度が異なる場合があります。正確な動作は、呼び出し時に指定されたコマンドラインフラグに依存する場合があります。たとえば、特定のフラグを指定することにより、GNU Cプリプロセッサをより標準に準拠させることができます。

コンパイラ固有のプリプロセッサ機能

#pragmaディレクティブはコンパイラ固有のディレクティブであり、コンパイラベンダーは独自の目的に使用できます。たとえば、#pragmaは、特定のエラーメッセージの抑制、ヒープの管理、スタックのデバッグなどを行うためによく使用されます。 OpenMP並列化ライブラリをサポートするコンパイラーは、#pragma omp parallel forを使用してforループを自動的に並列化できます。

C99では、いくつかの標準#pragmaディレクティブが導入され、#pragma STDC ...という形式を取り、浮動小数点実装の制御に使用されています。

  • 多くの実装は、トライグラフをサポートしていないか、デフォルトでそれらを置き換えません。
  • 多くの実装(GNU、Intel、Microsoft、IBMのCコンパイラなど)は、出力に警告メッセージを出力するが、コンパイルプロセスを停止しない非標準のディレクティブを提供します。典型的な使用法は、いくつかの古いコードの使用について警告することです。古いコードは非推奨になり、互換性の理由でのみ含まれています。例えば:

(GNU、Intel、IBM)

#warning「非推奨のABCを使用しないでください。代わりにXYZを使用してください。」

(Microsoft)

#pragma message( "非推奨のABCは使用しないでください。代わりにXYZを使用してください。")
  • 一部のUnixプリプロセッサは、伝統的に「アサーション」を提供していましたが、これはプログラミングで使用されるアサーションとほとんど類似していません。
  • GCCは、同じ名前のヘッダーをチェーンするために#include_nextを提供します。
  • Objective-Cプリプロセッサには#importがあります。#importは#includeに似ていますが、ファイルは1回しか含まれません。

その他の用途

Cプリプロセッサは、付属のコンパイラとは別に呼び出すことができるため、異なる言語で個別に使用できます。注目すべき例には、非推奨となったimakeシステムでの使用や、Fortranの前処理があります。ただし、汎用プリプロセッサとしての使用は制限されています。入力言語は十分にC言語に似ていなければなりません。 Fortranの前処理には、Cプリプロセッサのより柔軟なバリアントであるGPPが推奨されます。 GNU Fortranコンパイラーは、特定のファイル拡張子が使用されている場合、Fortranコードをコンパイルする前にcppを自動的に呼び出します。インテルは、同様の機能を備えたifortコンパイラーで使用するFortranプリプロセッサーfppを提供しています。

GPPは、ほとんどのアセンブリ言語でも問題なく機能します。 GNUは、プリプロセッサの実装のドキュメントで、アセンブリをC、C ++、Objective-Cの対象言語の1つとして言及しています。これには、アセンブラ構文がGPP構文と競合しないことが必要です。つまり、#で始まる行がなく、gppが文字列リテラルとして解釈するため無視する二重引用符は、それ以外の構文上の意味を持たないことを意味します。

Cプリプロセッサはチューリング完全ではありませんが、非常に近いものです。再帰的な計算を指定できますが、実行される再帰の量の上限は固定されています。ただし、Cプリプロセッサは、汎用プログラミング言語になるように設計されておらず、適切に機能しません。 Cプリプロセッサには、再帰マクロ、引用による選択的拡張、条件付き文字列評価など、他のプリプロセッサの機能がないため、m4などのより一般的なマクロプロセッサと比較して非常に制限されています。