サーチ…


前書き

Cプリプロセッサは、コードの実際のコンパイルの前に実行される単純なテキストパーサ/リプレイサです。 C(およびそれ以降のC ++)言語の使用を拡張し、容易にするために使用され、次の目的で使用できます。

a。 #includeを使って他のファイルインクルードする

b。 #defineを使用してテキスト置換マクロ#define

c。 #if #ifdefを使用した条件付きコンパイル

d。 プラットフォーム/コンパイラ固有のロジック (条件付きコンパイルの拡張として)

備考

プリプロセッサの文は、ソースファイルがコンパイラに渡される前に実行されます。非常に低いレベルの条件付きロジックが可能です。プリプロセッサの構造体(例えばオブジェクトのようなマクロ)は通常の関数のように型付けされていないので(コンパイル前に前処理ステップが発生する)、コンパイラは型チェックを強制することができないので注意深く使用する必要があります。

ガードを含める

ヘッダファイルは、他のヘッダファイルによって含まれることがあります。したがって、複数のヘッダーを含むソースファイル(コンパイル単位)は、間接的に複数のヘッダーを複数回含むことがあります。このようなヘッダファイルに複数の定義が含まれていると、コンパイラは(1つの2003 C ++標準の§3.2など)One Definition Ruleの違反を検出し、診断とコンパイルに失敗します。

ヘッダーガードまたはマクロガードとも呼ばれる「インクルードガード」を使用すると、複数のインクルードが防止されます。これらは、プリプロセッサの#define#ifndef#define #endifディレクティブを使用して実装されています。

例えば

// Foo.h
#ifndef FOO_H_INCLUDED 
#define FOO_H_INCLUDED

class Foo    //  a class definition
{
};

#endif

インクルードガードを使用する主な利点は、すべての標準準拠のコンパイラとプリプロセッサで動作することです。

ただし、プロジェクトで使用されるすべてのヘッダー内でマクロが一意であることを確認する必要があるため、ガードをインクルードすると、開発者にとって問題が発生します。具体的には、2つ(またはそれ以上)のヘッダーがFOO_H_INCLUDEDをインクルードガードとして使用する場合、コンパイル単位に含まれる最初のヘッダーは、他のヘッダーがインクルードされることを効果的に防止します。プロジェクトで使用されるヘッダーファイルを含むサードパーティ製ライブラリの数を共通に使用すると、特定の課題が発生します。

また、インクルードガードで使用されるマクロがヘッダーファイルで定義された他のマクロと競合しないようにする必要があります。

ほとんどのC ++実装では、 #pragma onceディレクティブもサポートされてい#pragma onceこれは、ファイルが1回のコンパイルで1回だけ含まれることを保証します。 デファクトスタンダードですが、ISO C ++標準の一部ではありません。例えば:

// Foo.h
#pragma once

class Foo
{
};

#pragma onceはインクルードガードに関連するいくつかの問題を回避しますが、標準で定義されている#pragmaは本質的にコンパイラ固有のフックであり、サポートしていないコンパイラによって黙って無視されます。 #pragma onceを使用するプロジェクトは、それをサポートしていないコンパイラに移植するのが難しくなります。

C ++の多くのコーディングガイドラインと保証標準では、ヘッダーファイルを#includeする以外に、ヘッダーにインクルードガードを配置する目的以外に、プリプロセッサの使用を特に禁止しています。

条件付きロジックおよびクロスプラットフォーム処理

簡単に言えば、条件付き前処理ロジックは、マクロ定義を使用してコンパイルにコードロジックを使用可能または使用不可にすることに関するものです。

3つの有名なユースケースがあります:

  • 同じアプリの候補となる可能性のある別のアプリプロファイル (デバッグ、リリース、テスト、最適化など)(余分なロギングなど)
  • クロスプラットフォームのコンパイル - 単一のコードベース、複数のコンパイルプラットフォーム
  • 多少の異なる機能を備えた複数のアプリケーションバージョン (例えば、ソフトウェアのBasic、Premium、Proバージョン)に共通のコードベースを利用する。

例a:ファイルを削除するためのクロスプラットフォームのアプローチ(例):

#ifdef _WIN32
#include <windows.h> // and other windows system files
#endif
#include <cstdio>

bool remove_file(const std::string &path) 
{
#ifdef _WIN32
  return DeleteFile(path.c_str());
#elif defined(_POSIX_VERSION) || defined(__unix__)
  return (0 == remove(path.c_str()));
#elif defined(__APPLE__)
  //TODO: check if NSAPI has a more specific function with permission dialog
  return (0 == remove(path.c_str()));
#else 
#error "This platform is not supported"
#endif
}

_WIN32__APPLE__ _WIN32 、または__unix__ようなマクロは、通常、対応する実装によって事前に定義されています。

例b:デバッグビルドの追加ログを有効にする:

void s_PrintAppStateOnUserPrompt()
{
    std::cout << "--------BEGIN-DUMP---------------\n"
              << AppState::Instance()->Settings().ToString() << "\n"
#if ( 1 == TESTING_MODE ) //privacy: we want user details only when testing
              << ListToString(AppState::UndoStack()->GetActionNames())
              << AppState::Instance()->CrntDocument().Name() 
              << AppState::Instance()->CrntDocument().SignatureSHA() << "\n"
#endif
              << "--------END-DUMP---------------\n"
}

例c:別の製品ビルドでプレミアム機能を有効にする(注:これは例示であり、アプリケーションを再インストールせずに機能のロックを解除することをお勧めします)

void MainWindow::OnProcessButtonClick()
{
#ifndef _PREMIUM
    CreatePurchaseDialog("Buy App Premium", "This feature is available for our App Premium users. Click the Buy button to purchase the Premium version at our website");
    return;
#endif
    //...actual feature logic here
}

いくつかの一般的なトリック:

呼び出し時のシンボルの定義:

プリプロセッサは、事前定義されたシンボルで呼び出すことができます(オプションの初期化を使用)。たとえば、このコマンド( gcc -Eはプリプロセッサだけを実行します)

gcc -E -DOPTIMISE_FOR_OS_X -DTESTING_MODE=1 Sample.cpp

#define OPTIMISE_FOR_OS_X#define TESTING_MODE 1がSample.cppの先頭に追加されたのと同じ方法でSample.cppを処理します。

マクロが定義されていることを確認する:

マクロが定義されておらず、その値が比較またはチェックされている場合、ほとんどの場合、プリプロセッサは値を0と仮定します。これにはいくつかの方法があります。 1つのアプローチは、デフォルト設定が0として表され、すべての変更(例えば、アプリケーションビルドプロファイルへの変更)が明示的に実行される必要があるということです(デフォルトではENABLE_EXTRA_DEBUGGING = 0、デフォルトでは-DENABLE_EXTRA_DEBUGGING = 1が上書きされます)。もう1つのアプローチは、すべての定義とデフォルトを明示的にすることです。これは#ifndef#errorディレクティブを組み合わせて使用​​できます。

#ifndef (ENABLE_EXTRA_DEBUGGING)
// please include DefaultDefines.h if not already included.
#    error "ENABLE_EXTRA_DEBUGGING is not defined"
#else
#    if ( 1 == ENABLE_EXTRA_DEBUGGING )
  //code
#    endif
#endif

マクロ

マクロは、オブジェクトのようなマクロと機能的なマクロの2つの主要なグループに分類されます。マクロは、コンパイルプロセスの早い段階でトークンの置換として扱われます。これは、コードの大きな部分(または繰り返し部分)をプリプロセッサマクロに抽象化できることを意味します。

// This is an object-like macro
#define    PI         3.14159265358979

// This is a function-like macro.
// Note that we can use previously defined macros
// in other macro definitions (object-like or function-like)
// But watch out, its quite useful if you know what you're doing, but the
// Compiler doesnt know which type to handle, so using inline functions instead
// is quite recommended (But e.g. for Minimum/Maximum functions it is quite useful)
#define    AREA(r)    (PI*(r)*(r))

// They can be used like this:
double pi_macro   = PI;
double area_macro = AREA(4.6);

Qtライブラリーは、ユーザーがQObjectを拡張するユーザー定義クラスの先頭にQ_OBJECTマクロを宣言することによって、このテクニックを使用してメタオブジェクト・システムを作成します。

マクロ名は通常、すべての大文字で書かれており、通常のコードとの区別を容易にします。これは要件ではありませんが、単に多くのプログラマが良いスタイルと考えるだけです。


オブジェクトのようなマクロが見つかった場合、そのマクロの名前がその定義に置き換えられて、単純なコピー&ペースト操作として展開されます。関数のようなマクロに遭遇すると、その名前とそのパラメータの両方が展開されます。

double pi_squared = PI * PI;
// Compiler sees:
double pi_squared = 3.14159265358979 * 3.14159265358979;

double area = AREA(5);
// Compiler sees:
double area = (3.14159265358979*(5)*(5))

このため、関数のようなマクロパラメータは、上記のAREA()ように、括弧内に囲まれることがよくあります。これは、マクロ展開中に発生する可能性のあるバグ、特に単一のマクロパラメータが複数の実際の値で構成されるバグを防ぐためです。

#define BAD_AREA(r) PI * r * r

double bad_area = BAD_AREA(5 + 1.6);
// Compiler sees:
double bad_area = 3.14159265358979 * 5 + 1.6 * 5 + 1.6;

double good_area = AREA(5 + 1.6);
// Compiler sees:
double good_area = (3.14159265358979*(5 + 1.6)*(5 + 1.6));

また、この単純な拡張のために、予期しない副作用を防ぐために、マクロに渡されるパラメータに注意する必要があります。評価中にパラメータが変更された場合、拡張マクロで使用されるたびにパラメータが変更されますが、これは通常は必要なものではありません。展開が何かを壊すのを防ぐために、マクロがかっこでパラメータを囲む場合でも、これは当てはまります。

int oops = 5;
double incremental_damage = AREA(oops++);
// Compiler sees:
double incremental_damage = (3.14159265358979*(oops++)*(oops++));

さらに、マクロは型安全性を提供しないため、型の不一致に関する理解しにくいエラーにつながります。


プログラマが通常セミコロンで行を終了させるので、スタンドアロン行として使用されるマクロはセミコロンを「飲み込む」ように設計されていることがよくあります。余分なセミコロンによって意図しないバグが発生するのを防ぎます。

#define IF_BREAKER(Func) Func();

if (some_condition)
    // Oops.
    IF_BREAKER(some_func);
else
    std::cout << "I am accidentally an orphan." << std::endl;

この例では、誤った二重セミコロンがif...elseブロックを壊し、コンパイラがelseifマッチさせifます。これを防ぐために、セミコロンはマクロ定義から除外されます。セミコロンは、セミコロンを使用した直後にセミコロンを「取り止め」ます。

#define IF_FIXER(Func) Func()

if (some_condition)
    IF_FIXER(some_func);
else
    std::cout << "Hooray!  I work again!" << std::endl;

末尾のセミコロンを残しておくと、現在のステートメントを終了することなくマクロを使用することができます。これは有益です。

#define DO_SOMETHING(Func, Param) Func(Param, 2)

// ...

some_function(DO_SOMETHING(some_func, 3), DO_SOMETHING(some_func, 42));

通常、マクロ定義は行末で終了します。ただし、マクロが複数の行をカバーする必要がある場合は、行末にバックスラッシュを使用してこれを示すことができます。このバックスラッシュは、行内の最後の文字でなければなりません。これは、プリプロセッサに対して、次の行を現在の行に連結して1行として扱うことを示します。これは連続して複数回使用できます。

#define TEXT "I \
am \
many \
lines."

// ...

std::cout << TEXT << std::endl; // Output:   I am many lines.

これは、複数の行をカバーする必要のある、複雑な関数のようなマクロで特に便利です。

#define CREATE_OUTPUT_AND_DELETE(Str) \
    std::string* tmp = new std::string(Str); \
    std::cout << *tmp << std::endl; \
    delete tmp;

// ...

CREATE_OUTPUT_AND_DELETE("There's no real need for this to use 'new'.")

より複雑な関数のようなマクロの場合、実際の関数と同様に、可能な名前の衝突を防止したり、マクロの最後にオブジェクトを破棄したりするための独自のスコープを与えることは便利です。これはdo-whileブロックに囲まれている0の間 、一般的なイディオムです。このブロックには一般的にセミコロンが付いていないため、セミコロンを呑み込むことができます。

#define DO_STUFF(Type, Param, ReturnVar) do { \
    Type temp(some_setup_values); \
    ReturnVar = temp.process(Param); \
} while (0)

int x;
DO_STUFF(MyClass, 41153.7, x);

// Compiler sees:

int x;
do {
    MyClass temp(some_setup_values);
    x = temp.process(41153.7);
} while (0);

variadicマクロもあります。同様に可変引数関数に、これらは、可変個の引数を取り、その後、特別な「可変引数」パラメータの代わりに、それらすべてを展開する__VA_ARGS__

#define VARIADIC(Param, ...) Param(__VA_ARGS__)

VARIADIC(printf, "%d", 8);
// Compiler sees:
printf("%d", 8);

展開中、 __VA_ARGS__は定義のどこにでも配置でき、正しく展開されることに注意してください。

#define VARIADIC2(POne, PTwo, PThree, ...) POne(PThree, __VA_ARGS__, PTwo)

VARIADIC2(some_func, 3, 8, 6, 9);
// Compiler sees:
some_func(8, 6, 9, 3);

引数がゼロの可変パラメータの場合、異なるコンパイラが末尾のカンマを異なる方法で処理します。 Visual Studioなどのコンパイラの中には、特別な構文を使わずにカンマを静かに飲み込むものがあります。 GCCのような他のコンパイラでは、 __VA_ARGS__直前に##を置く必要があります。このため、移植性が懸念される場合には、条件付きでマクロを定義することが賢明です。

// In this example, COMPILER is a user-defined macro specifying the compiler being used.

#if       COMPILER == "VS"
    #define VARIADIC3(Name, Param, ...) Name(Param, __VA_ARGS__)
#elif     COMPILER == "GCC"
    #define VARIADIC3(Name, Param, ...) Name(Param, ##__VA_ARGS__)
#endif /* COMPILER */

プリプロセッサエラーメッセージ

プリプロセッサを使用してコンパイルエラーを生成できます。これは、サポートされていないプラットフォームまたはサポートされていないコンパイラにいるかどうかをユーザーに通知するなど、いくつかの理由が含まれています。

gccのバージョンが3.0.0以前であればエラーを返します。

#if __GNUC__ < 3
#error "This code requires gcc > 3.0.0"
#endif

アップルコンピュータでコンパイルする場合は、エラーを返します。

#ifdef __APPLE__
#error "Apple products are not supported in this release"
#endif

定義済みのマクロ

あらかじめ定義されたマクロは、コンパイラによって定義されたマクロです(ソースファイル内のユーザ定義とは異なります)。これらのマクロは、ユーザによって再定義されたり、定義されたりしてはなりません。

次のマクロは、C ++標準によって事前定義されています。

  • __LINE__このマクロは上で使用される行の行番号を含み、によって変更することができる#lineディレクティブ。
  • __FILE__は、このマクロが使用されているファイルのファイル名が含まれており、 #lineディレクティブで変更できます。
  • __DATE__ (に日付を含む"Mmm dd yyyy"への呼び出しによって得られたかのようにうーんがフォーマットされたファイルのコンパイル、の形式) std::asctime()
  • __TIME__には、ファイルコンパイルの時間( "hh:mm:ss"形式)が含まれます。
  • __cplusplusは、(適合する)C ++コンパイラによって定義され、C ++ファイルをコンパイルします。その値はすなわち、コンパイラはと完全に準拠している標準バージョンである199711L 、C ++ 98とC ++ 03のため201103L C ++ 11とのため201402L C ++ 14標準のために。
c ++ 11
  • __STDC_HOSTED__は、実装がホストされている場合は1フリースタンディングの場合は0定義されます。
c ++ 17
  • __STDCPP_DEFAULT_NEW_ALIGNMENT__にはsize_tリテラルが含まれていsize_tリテラルは、アラインメント・__STDCPP_DEFAULT_NEW_ALIGNMENT__ operator newへの呼び出しに使用されるアラインメントです。

さらに、次のマクロは実装によって事前定義されていても、存在していなくてもかまいません。

  • __STDC__は実装依存の意味を持ち、通常は完全なC標準準拠を示すためにファイルをCとしてコンパイルするときにのみ定義されます。 (あるいは、コンパイラがこのマクロをサポートしないことにした場合は決してしない)。
c ++ 11
  • __STDC_VERSION__はインプリメンテーション依存の意味を持ち、 __cplusplusがC ++バージョンと同じように、その値は通常Cバージョンです。 (または、コンパイラがこのマクロをサポートしないことを決定した場合でも、定義されていません)。
  • (uintmax_t)'x' != (uintmax_t)L'x' )のように、 __STDC_MB_MIGHT_NEQ_WC__1に定義されている場合、基本文字セットの狭いエンコーディングの値は、
  • __STDC_ISO_10646__は、 wchar_tがUnicodeとしてエンコードされている場合に定義され、サポートされている最新のUnicodeリビジョンを示すyyyymmL形式の整数定数に展開されます。
  • __STDCPP_STRICT_POINTER_SAFETY__は、実装が厳密なポインタの安全性を持っている場合は1に定義されます (そうでない場合、 ポインタの安全性緩和されます
  • __STDCPP_THREADS__1に設定されます。プログラムが複数の実行スレッドを持つことができる場合( 自立実装に適用 - ホスト 実装では常に複数のスレッドを持つことができます)

__func__はマクロではなく、あらかじめ定義された関数ローカル変数です。これは、使用される関数の名前を、実装定義形式の静的文字配列として含みます。

これらの標準的な事前定義されたマクロの上に、コンパイラはあらかじめ定義されたマクロを持つことができます。それらを学ぶには、コンパイラのドキュメントを参照する必要があります。例えば:

いくつかのマクロは、いくつかの機能のサポートを照会するだけです。

#ifdef __cplusplus // if compiled by C++ compiler
extern "C"{ // C code has to be decorated
   // C library header declarations here
}
#endif

他はデバッグに非常に便利です:

c ++ 11
bool success = doSomething( /*some arguments*/ );
if( !success ){
    std::cerr << "ERROR: doSomething() failed on line " << __LINE__ - 2
              << " in function " << __func__ << "()"
              << " in file " << __FILE__
              << std::endl;
}

そして些細なバージョン管理のための他のもの:

int main( int argc, char *argv[] ){
    if( argc == 2 && std::string( argv[1] ) == "-v" ){
        std::cout << "Hello World program\n"
                  << "v 1.1\n" // I have to remember to update this manually
                  << "compiled: " << __DATE__ << ' ' << __TIME__ // this updates automagically
                  << std::endl;
    }
    else{
        std::cout << "Hello World!\n";
    }
}

Xマクロ

コンパイル時に繰り返しコード構造を生成するための慣用技術。

Xマクロは、リストとリストの実行という2つの部分で構成されています。

例:

#define LIST \
    X(dog)   \
    X(cat)   \
    X(racoon)

// class Animal {
//  public:
//    void say();
// };

#define X(name) Animal name;
LIST
#undef X

int main() {
#define X(name) name.say();
    LIST
#undef X

    return 0;
}

これはプリプロセッサによって次のように展開されます。

Animal dog;
Animal cat;
Animal racoon;

int main() {
    dog.say();
    cat.say();
    racoon.say();

    return 0;
}    

リストが大きくなるにつれ(100個以上の要素)、このテクニックは過度のコピー貼り付けを避けるのに役立ちます。

出典: https : //en.wikipedia.org/wiki/X_Macro

参照: Xマクロ


LISTを使用する前に継ぎ目のない無関係のX定義していない場合は、マクロ名を引数として渡すこともできます。

#define LIST(MACRO) \
    MACRO(dog) \
    MACRO(cat) \
    MACRO(racoon)

ここで、リストを展開するときにどのマクロを使うべきかを明示的に指定します。

#define FORWARD_DECLARE_ANIMAL(name) Animal name;
LIST(FORWARD_DECLARE_ANIMAL)

MACROを呼び出すたびにリストに対して一定の追加パラメータが必要な場合は、バリデージックマクロを使用できます

//a walkaround for Visual studio
#define EXPAND(x) x

#define LIST(MACRO, ...) \
    EXPAND(MACRO(dog, __VA_ARGS__)) \
    EXPAND(MACRO(cat, __VA_ARGS__)) \
    EXPAND(MACRO(racoon, __VA_ARGS__))

最初の引数はLISTによって提供され、残りはLIST呼び出しでユーザーによって提供されます。例えば:

#define FORWARD_DECLARE(name, type, prefix) type prefix##name;
LIST(FORWARD_DECLARE,Animal,anim_)
LIST(FORWARD_DECLARE,Object,obj_)

に拡大する

Animal anim_dog;
Animal anim_cat;
Animal anim_racoon;
Object obj_dog;
Object obj_cat;
Object obj_racoon;        

#pragma once

ほとんどのC ++実装では、 #pragma onceディレクティブがサポートされてい#pragma onceこれは、ファイルが1回のコンパイルで1回だけ含まれることを保証します。 ISO C ++標準の一部ではありません。例えば:

// Foo.h
#pragma once

class Foo
{
};

#pragma once はインクルードガードに関連するいくつかの問題を回避しますが、標準で定義されている#pragmaは本質的にコンパイラ固有のフックであり、サポートしていないコンパイラによって黙って無視されます。 #pragma onceを使用するプロジェクトは、標準に準拠するように変更する必要があります。

いくつかのコンパイラ、特にプリコンパイルされたヘッダーを使用するコンパイラでは、 #pragma onceを使用するとコンパイルプロセスが大幅に高速化される可能性があります。同様に、いくつかのプリプロセッサは、どのヘッダが使用されているかを追跡することによってコンパイルの高速化を達成する。 #pragma once#pragma once両方が使用されている場合の純便益は、実装に依存し、コンパイル時間の増加または減少のいずれかになります。

#pragma onceと組み合わせて警備員を含めるヘッダファイルの推奨レイアウトウィンドウ上のMFCベースのアプリケーションを作成、およびVisual Studioので生成されたたadd classadd dialogadd windowsウィザードが。したがって、それらをC ++ Windowsアプリケーションで組み合わせることは非常に一般的です。

プリプロセッサ演算子

#演算子またはstringizing演算子は、マクロパラメータを文字列リテラルに変換するために使用されます。引数を持つマクロでのみ使用できます。

// preprocessor will convert the parameter x to the string literal x
#define PRINT(x) printf(#x "\n")

PRINT(This line will be converted to string by preprocessor);
// Compiler sees
printf("This line will be converted to string by preprocessor""\n");

コンパイラは2つの文字列を連結し、最後のprintf()引数は文字列リテラルになり、最後に改行文字が付きます。

プリプロセッサは、マクロ引数の前または後のスペースを無視します。したがって、print文の下で同じ結果が得られます。

PRINT(   This line will be converted to string by preprocessor );

文字列リテラルのパラメータが二重引用符()の前のようなエスケープシーケンスを必要とする場合、プリプロセッサによって自動的に挿入されます。

PRINT(This "line" will be converted to "string" by preprocessor); 
// Compiler sees
printf("This \"line\" will be converted to \"string\" by preprocessor""\n");

##演算子またはトークンペースト演算子は、マクロの2つのパラメータまたはトークンを連結するために使用されます。

// preprocessor will combine the variable and the x
#define PRINT(x) printf("variable" #x " = %d", variable##x)

int variableY = 15;
PRINT(Y);
//compiler sees
printf("variable""Y"" = %d", variableY);

最終的な出力は

variableY = 15


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow