この主張に何度も遭遇し 、それが何を意味するのか理解できない場合. 結果のコードは通常の C コンパイラを使用してコンパイルされるため、他のコードと同じように (またはほとんど) タイプ チェックされません。
では、なぜマクロはタイプ セーフではないのでしょうか。それが彼らが悪と見なされるべき主な理由の1つであるようです.
典型的な "max" マクロと関数を考えてみましょう:
#define MAX(a,b) a < b ? a : b
int max(int a, int b) {return a < b ? a : b;}
マクロは関数のように型安全ではないと人々が言うときの意味は次のとおりです。
関数の呼び出し元が書き込む場合
char *foo = max("abc","def");
コンパイラは警告します。
一方、マクロの呼び出し元が次のように記述した場合:
char *foo = MAX("abc", "def");
プリプロセッサはそれを次のものに置き換えます。
char *foo = "abc" < "def" ? "abc" : "def";
これは問題なくコンパイルされますが、ほとんどの場合、必要な結果が得られません。
さらに、もちろん副作用は異なります。関数の場合を考えてみましょう。
int x = 1, y = 2;
int a = max(x++,y++);
max() 関数は x と y の元の値で動作し、ポストインクリメントは関数が戻った後に有効になります。
マクロの場合:
int x = 1, y = 2;
int b = MAX(x++,y++);
その 2 行目は次のように前処理されます。
int b = x++ < y++ ? x++ : y++;
繰り返しますが、コンパイラの警告やエラーはありませんが、期待した動作にはなりません。
マクロは型を認識しないため、型安全ではありません。
整数のみを受け取るようにマクロに指示することはできません。プリプロセッサはマクロの使用を認識し、一連のトークン (マクロとその引数) を別の一連のトークンに置き換えます。これは正しく使えば強力な機能ですが、間違った使い方をするのは簡単です。
関数を使用すると、関数を定義できvoid f(int, int)
、 f の戻り値を使用しようとしたり、文字列を渡したりしようとすると、コンパイラがフラグを立てます。
マクロで - チャンスはありません。行われる唯一のチェックは、正しい数の引数が与えられていることです。次に、トークンを適切に置き換えて、コンパイラに渡します。
#define F(A, B)
F(1, 2)
、またはF("A", 2)
またはF(1, (2, 3, 4))
または...を呼び出すことができます
マクロ内の何かがある種のタイプ セーフを必要とする場合、コンパイラからエラーが発生する場合と発生しない場合があります。しかし、それはプリプロセッサのせいではありません。
数値を期待するマクロに文字列を渡すと、非常に奇妙な結果が得られる可能性があります。コンパイラからのきしみ音なしで、文字列アドレスを数値として使用することになる可能性があるからです。
マクロはプリプロセッサによって処理され、プリプロセッサは型を理解しないため、間違った型の変数を喜んで受け入れます。
これは通常、関数のようなマクロの場合にのみ問題となり、型エラーは、プリプロセッサがキャッチしなくてもコンパイラによってキャッチされることがよくありますが、これは保証されていません。
例
Windows API では、エディット コントロールにバルーン ヒントを表示する場合は、Edit_ShowBalloonTipを使用します。Edit_ShowBalloonTip は、編集コントロールへのハンドルと EDITBALLOONTIP 構造体へのポインターの 2 つのパラメーターを取るように定義されています。ただし、Edit_ShowBalloonTip(hwnd, peditballoontip);
実際には次のように評価されるマクロです。
SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)(peditballoontip));
コントロールの構成は通常、メッセージを送信することによって行われるためEdit_ShowBalloonTip
、実装で型キャストを行う必要がありますが、インライン関数ではなくマクロであるため、peditballoontip パラメーターで型チェックを行うことはできません。
余談
興味深いことに、C++ のインライン関数はタイプ セーフすぎる場合があります。標準の C MAX マクロを考えてみましょう
#define MAX(a, b) ((a) > (b) ? (a) : (b))
およびその C++ インライン バージョン
template<typename T>
inline T max(T a, T b) { return a > b ? a : b; }
MAX(1, 2u) は期待どおりに機能しますが、max(1, 2u) は機能しません。(1 と 2u は異なる型であるため、両方で max をインスタンス化することはできません。)
これは、ほとんどの場合にマクロを使用するための実際の議論ではありません(マクロは依然として悪です) が、C および C++ のタイプ セーフの興味深い結果です。
マクロは関数よりも型安全性が低い場合があります。例えば
void printlog(int iter, double obj)
{
printf("%.3f at iteration %d\n", obj, iteration);
}
引数を逆にしてこれを呼び出すと、切り捨てと誤った結果が生じますが、危険はありません。対照的に、
#define PRINTLOG(iter, obj) printf("%.3f at iteration %d\n", obj, iter)
未定義の動作を引き起こします。公平を期すために、GCC は後者については警告しますが、前者については警告しませんが、それは知っているprintf
ためです。他の varargs 関数の場合、結果は悲惨な結果になる可能性があります。