サーチ…
前書き
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
ブロックを壊し、コンパイラがelse
をif
マッチさせ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標準のために。
-
__STDC_HOSTED__
は、実装がホストされている場合は1
、 フリースタンディングの場合は0
定義されます。
-
__STDCPP_DEFAULT_NEW_ALIGNMENT__
にはsize_t
リテラルが含まれていsize_t
リテラルは、アラインメント・__STDCPP_DEFAULT_NEW_ALIGNMENT__
operator new
への呼び出しに使用されるアラインメントです。
さらに、次のマクロは実装によって事前定義されていても、存在していなくてもかまいません。
-
__STDC__
は実装依存の意味を持ち、通常は完全なC標準準拠を示すためにファイルをCとしてコンパイルするときにのみ定義されます。 (あるいは、コンパイラがこのマクロをサポートしないことにした場合は決してしない)。
-
__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
他はデバッグに非常に便利です:
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 class
、 add dialog
、 add 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