マクロを使用する方が関数を使用するよりも優れている例とケースを常に見てきました。
関数と比較したマクロの欠点を例で説明してもらえますか?
マクロは、テキストの置換に依存し、タイプチェックを実行しないため、エラーが発生しやすくなります。たとえば、次のマクロ:
#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++プログラミング言語を参照してください。
マクロ機能:
機能機能:
副作用は大きなものです。ここに典型的なケースがあります:
#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;
各行の終わりに\
が必要です。
マクロを単一の式にしない限り、マクロは何も「返す」ことはできません:
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
が最初に評価されます。
#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;
}
疑わしい場合は、関数(またはインライン関数)を使用してください。
ただし、ここでの回答は、愚かな事故が発生する可能性があるため、マクロは悪であるという単純な見方をするのではなく、マクロに関する問題を主に説明しています。
あなたは落とし穴に気づき、それらを避けることを学ぶことができます。次に、正当な理由がある場合にのみマクロを使用します。
マクロを使用する利点がある特定のexceptionalケースがあります。
va_args
を使用する代わりに、可変数の引数を異なる関数にマップできます。__FILE__
、__LINE__
、__func__
)。事前/事後条件、失敗時のassert
、または静的アサートまでチェックして、不適切な使用時にコードがコンパイルされないようにします(デバッグビルドに役立ちます)。struct
メンバーの存在のチェックなどのテストを実行できます。func(FOO, "FOO");
、文字列を展開するマクロを定義できますfunc_wrapper(FOO);
inline
関数はオプションの場合があります)。確かに、これらの一部は、標準Cではないコンパイラ拡張機能に依存しています。つまり、移植性の低いコードになる場合や、ifdef
を使用する必要がある場合があるため、コンパイラがサポートする場合にのみ利用されます。
マクロのエラーの最も一般的な原因の1つ(たとえば、x++
を渡す、マクロが複数回インクリメントする可能性がある)であるため、これに注意してください。
引数の複数のインスタンス化による副作用を回避するマクロを作成できます。
さまざまなタイプで動作し、C11をサポートするsquare
マクロが必要な場合は、これを行うことができます...
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
関数を使用できることです。
パラメーターとコードの型チェックが繰り返されないため、コードが膨張する可能性があります。マクロ構文は、セミコロンまたは優先順位が邪魔になる可能性のある奇妙なEdgeのケースをいくつも引き起こす可能性があります。ここにいくつかのマクロを示すリンクがあります evil
マクロの1つの欠点は、デバッガーが展開されたマクロを持たないソースコードを読み取るため、マクロでデバッガーを実行することが必ずしも有用ではないことです。言うまでもなく、関数の場合のようにマクロ内にブレークポイントを設定することはできません。
この答えに追加する..
マクロは、プリプロセッサによってプログラムに直接置き換えられます(基本的にプリプロセッサディレクティブであるため)。したがって、それらは必然的にそれぞれの関数よりも多くのメモリ空間を使用します。一方、関数は呼び出されて結果を返すためにより多くの時間を必要とし、マクロを使用することでこのオーバーヘッドを回避できます。
また、マクロには、異なるプラットフォームでのプログラムの移植性を支援できる特別なツールがいくつかあります。
関数とは異なり、マクロの引数にデータ型を割り当てる必要はありません。
全体として、それらはプログラミングに役立つツールです。また、状況に応じて、マクロ命令と関数の両方を使用できます。
関数は型チェックを行います。これにより、安全性がさらに高まります。
上記の答えでは、マクロよりも関数の利点の1つが非常に重要だとは思いませんでした。
関数は引数として渡すことができますが、マクロは渡すことができません。
具体的な例:別の文字列内で検索する明示的な文字リストではなく、受け入れる「strpbrk」関数の代替バージョンを記述したい場合、a(ポインタ)テスト(ユーザー定義)に合格する文字が見つかるまで0を返す関数。これを行う理由の1つは、他の標準ライブラリ関数を利用できるようにするためです。句読点でいっぱいの明示的な文字列を提供する代わりに、代わりにctype.hの「ispunct」などを渡すことができます。マクロ、これは機能しません。
他にもたくさんの例があります。たとえば、関数ではなくマクロで比較を行う場合、stdlib.hの「qsort」に渡すことはできません。
Pythonの類似した状況は、バージョン2とバージョン3での「印刷」です(通過不可能なステートメントと通過可能な関数)。
マクロに引数として関数を渡すと、毎回評価されます。たとえば、最も人気のあるマクロの1つを呼び出す場合:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
そのように
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));
functionThatTakeLongTimeは5回評価され、パフォーマンスが大幅に低下する可能性があります