回答:
マクロはテキスト置換に依存し、型チェックを実行しないため、エラーが発生しやすくなります。たとえば、次のマクロ:
#define square(a) a * a
整数と一緒に使用すると正常に動作します。
square(5) --> 5 * 5 --> 25
しかし、式で使用すると非常に奇妙なことをします:
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
引数を括弧で囲むことは役立ちますが、これらの問題を完全に排除するわけではありません。
マクロに複数のステートメントが含まれている場合、制御フロー構造で問題が発生する可能性があります。
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
これを修正するための通常の戦略は、ステートメントを「do {...} while(0)」ループ内に置くことです。
たまたま名前が同じでセマンティクスが異なるフィールドを含む2つの構造がある場合、同じマクロが両方で機能し、奇妙な結果になることがあります。
struct shirt
{
int numButtons;
};
struct webpage
{
int numButtons;
};
#define num_button_holes(shirt) ((shirt).numButtons * 4)
struct webpage page;
page.numButtons = 2;
num_button_holes(page) -> 8
最後に、次の例のようにデバッガーはマクロをステップ実行できないため、マクロはデバッグが困難で、奇妙な構文エラーまたはランタイムエラーを生成して理解する必要があります(gcc -Eなど)。
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
インライン関数と定数は、マクロに関するこれらの問題の多くを回避するのに役立ちますが、常に適用できるとは限りません。意図的に多態性の振る舞いを指定するためにマクロが使用されている場合、意図しない多態性を回避するのが難しい場合があります。C ++には、テンプレートを使用するなど、マクロを使用せずにタイプセーフな方法で複雑なポリモーフィックコンストラクトを作成するのに役立つ多くの機能があります。詳細については、StroustrupのC ++プログラミング言語を参照してください。
x++*x++
はx
2倍に増えるとは言えません。実際には、未定義の動作を呼び出します。つまり、コンパイラは自由に何でも実行できますx
。つまり、2回、1回、またはまったく増加しない可能性があります。エラーで中断したり、悪魔が鼻から飛び出すことさえあります。
マクロ機能:
機能の特徴:
副作用は大きなものです。これが典型的なケースです:
#define min(a, b) (a < b ? a : b)
min(x++, y)
以下に拡張されます:
(x++ < y ? x++ : y)
x
同じステートメントで2回インクリメントされます。(および未定義の動作)
複数行のマクロを書くのも面倒です:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
\
各行の終わりにaが必要です。
マクロは、1つの式にしない限り、何も「返す」ことができません。
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
GCCの式ステートメントを使用しない限り、マクロでそれを行うことはできません。(編集:カンマ演算子を使用することもできますが...見落としがちですが、それでも読みにくくなる可能性があります。)
操作の順序:(@ouahの厚意による)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
以下に拡張されます:
(x & 0xFF < 42 ? x & 0xFF : 42)
しかし&
、よりも優先順位が低くなり<
ます。したがって0xFF < 42
、最初に評価されます。
min(a & 0xFF, 42)
#define SQUARE(x) ((x)*(x))
int main() {
int x = 2;
int y = SQUARE(x++); // Undefined behavior even though it doesn't look
// like it here
return 0;
}
一方:
int square(int x) {
return x * x;
}
int main() {
int x = 2;
int y = square(x++); // fine
return 0;
}
struct foo {
int bar;
};
#define GET_BAR(f) ((f)->bar)
int main() {
struct foo f;
int a = GET_BAR(&f); // fine
int b = GET_BAR(&a); // error, but the message won't make much sense unless you
// know what the macro does
return 0;
}
に比べ:
struct foo {
int bar;
};
int get_bar(struct foo *f) {
return f->bar;
}
int main() {
struct foo f;
int a = get_bar(&f); // fine
int b = get_bar(&a); // error, but compiler complains about passing int* where
// struct foo* should be given
return 0;
}
疑わしい場合は、関数(またはインライン関数)を使用してください。
ただし、ここでの回答は主にマクロの問題を説明するものであり、馬鹿げた事故が発生する可能性があるためマクロは悪であるという単純な見方はありません。
あなたは落とし穴を認識し、それらを避けることを学ぶことができます。次に、正当な理由がある場合にのみマクロを使用します。
マクロを使用する利点がある特定の例外的なケースがあり、これらには次のものが含まれます。
va_args
。__FILE__
、__LINE__
、__func__
)。assert
コードが不適切に使用されてもコンパイルされないように、障害発生時の事前/事後条件、または静的アサートさえチェックします(ほとんどがデバッグビルドに役立ちます)。struct
キャストの前にメンバーが存在するかどうかのチェックなどの入力引数のテストを行うことができますfunc(FOO, "FOO");
にすることができます。例:... 、文字列を展開するマクロを定義できますfunc_wrapper(FOO);
inline
関数はオプションである可能性があるため、コンテキストに大きく依存します)。確かに、これらの一部は標準C以外のコンパイラー拡張に依存しています。つまり、移植性の低いコードになってifdef
しまうか、コードを組み込む必要があるため、コンパイラーがサポートする場合にのみ利用されます。
マクロでのエラーの最も一般的な原因の1つであるため、これに注意してください(x++
たとえば、マクロが複数回インクリメントする可能性がある場合に渡されます)。
引数の複数のインスタンス化による副作用を回避するマクロを書くことが可能です。
square
さまざまなタイプで機能し、C11をサポートするマクロが必要な場合は、これを行うことができます...
inline float _square_fl(float a) { return a * a; }
inline double _square_dbl(float a) { return a * a; }
inline int _square_i(int a) { return a * a; }
inline unsigned int _square_ui(unsigned int a) { return a * a; }
inline short _square_s(short a) { return a * a; }
inline unsigned short _square_us(unsigned short a) { return a * a; }
/* ... long, char ... etc */
#define square(a) \
_Generic((a), \
float: _square_fl(a), \
double: _square_dbl(a), \
int: _square_i(a), \
unsigned int: _square_ui(a), \
short: _square_s(a), \
unsigned short: _square_us(a))
これは、GCC、Clang、EKOPath、およびIntel C ++ (MSVCではない)でサポートされているコンパイラー拡張です。
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
したがって、マクロの欠点は、最初にこれらを使用することを知っておく必要があり、マクロがそれほど広くサポートされていないことです。
1つの利点は、この場合、square
多くの異なるタイプに同じ関数を使用できることです。
関数は型チェックを行います。これにより、安全性がさらに高まります。
この答えに追加..
マクロはプリプロセッサによってプログラムに直接置き換えられます(それらは基本的にプリプロセッサディレクティブであるため)。したがって、それらは必然的にそれぞれの関数よりも多くのメモリ空間を使用します。一方、関数を呼び出すと結果を返すには時間がかかるため、マクロを使用することでこのオーバーヘッドを回避できます。
また、マクロには、さまざまなプラットフォームでのプログラムの移植性に役立ついくつかの特別なツールがあります。
マクロは、関数とは異なり、引数にデータ型を割り当てる必要はありません。
全体的に、それらはプログラミングに役立つツールです。また、状況に応じてマクロ命令と関数の両方を使用できます。
上記の回答では、非常に重要であると考えるマクロに対する関数の利点の1つに気づきませんでした。
関数は引数として渡すことができますが、マクロはできません。
具体的な例:別の文字列内で検索する文字の明示的なリストではなく、文字が0になるまで0を返す(aへのポインター)関数を受け入れる標準の 'strpbrk'関数の代替バージョンを作成したいいくつかのテスト(ユーザー定義)に合格することがわかりました。これを行う理由の1つは、他の標準ライブラリ関数を利用できるようにするためです。句読点でいっぱいの明示的な文字列を提供する代わりに、代わりにctype.hの 'ispunct'を渡すことができます。'ispunct 'がマクロ、これは機能しません。
他にもたくさんの例があります。たとえば、比較が関数ではなくマクロによって行われる場合、それをstdlib.hの 'qsort'に渡すことはできません。
Pythonでの類似の状況は、バージョン2とバージョン3での「印刷」です(非受け渡し可能なステートメントと受け渡し可能な関数)。