web-dev-qa-db-ja.com

Cのマクロと機能

マクロを使用する方が関数を使用するよりも優れている例とケースを常に見てきました。

関数と比較したマクロの欠点を例で説明してもらえますか?

91
Kyrol

マクロは、テキストの置換に依存し、タイプチェックを実行しないため、エラーが発生しやすくなります。たとえば、次のマクロ:

#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++プログラミング言語を参照してください。

102
D Coetzee

マクロ機能

  • マクロは前処理済み
  • 型チェックなし
  • コード長増加
  • マクロを使用すると、副作用につながる可能性があります
  • 実行速度はFasterです
  • コンパイルマクロ名がマクロ値に置き換えられる前
  • 小さなコードが何度も現れる場合に便利
  • マクロはnotコンパイルエラーをチェックします

機能機能

  • 関数はコンパイル済み
  • 型チェックが完了しました
  • コードの長さは残ります同じ
  • いいえ副作用
  • 実行速度は遅い
  • 関数呼び出し中に、制御の転送が行われます
  • 大きなコードが何度も現れる場合に便利
  • 機能チェックコンパイルエラー
32
zangw

副作用は大きなものです。ここに典型的なケースがあります:

#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が最初に評価されます。

30
Mysticial

例1:

#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;
}

例2:

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;
}
13
Flexo

疑わしい場合は、関数(またはインライン関数)を使用してください。

ただし、ここでの回答は、愚かな事故が発生する可能性があるため、マクロは悪であるという単純な見方をするのではなく、マクロに関する問題を主に説明しています。
あなたは落とし穴に気づき、それらを避けることを学ぶことができます。次に、正当な理由がある場合にのみマクロを使用します。

マクロを使用する利点がある特定のexceptionalケースがあります。

  • 以下に示すように、汎用関数には、さまざまなタイプの入力引数で使用できるマクロがあります。
  • Cのva_argsを使用する代わりに、可変数の引数を異なる関数にマップできます。
    eg: https://stackoverflow.com/a/24837037/432509
  • それらは、オプションでデバッグ文字列などのローカル情報を含めることができます:
    __FILE____LINE____func__)。事前/事後条件、失敗時のassert、または静的アサートまでチェックして、不適切な使用時にコードがコンパイルされないようにします(デバッグビルドに役立ちます)。
  • 入力引数を検査します。入力引数のタイプ、sizeofのチェック、キャストする前にstructメンバーの存在のチェックなどのテストを実行できます。
    (多相型に役立つ可能性があります)
    または配列が長さの条件を満たしていることを確認してください。
    参照: https://stackoverflow.com/a/29926435/432509
  • 関数は型チェックを行うと述べていますが、Cは値も強制します(たとえば、ints/floats)。まれに、これが問題になる場合があります。入力引数に関する関数よりも厳密なマクロを記述することが可能です。参照: https://stackoverflow.com/a/25988779/432509
  • 関数のラッパーとしての使用。場合によっては、繰り返しを避けたい場合があります。例えば... func(FOO, "FOO");、文字列を展開するマクロを定義できますfunc_wrapper(FOO);
  • 呼び出し元のローカルスコープで変数を操作する場合、ポインターへのポインターの受け渡しは正常に機能しますが、マクロを使用するほうが問題は少ない場合があります。
    (ピクセルごとの操作の複数の変数への割り当ては、関数よりもマクロを好むかもしれない例です... inline関数はオプションの場合があります)

確かに、これらの一部は、標準Cではないコンパイラ拡張機能に依存しています。つまり、移植性の低いコードになる場合や、ifdefを使用する必要がある場合があるため、コンパイラがサポートする場合にのみ利用されます。


複数の引数のインスタンス化の回避

マクロのエラーの最も一般的な原因の1つ(たとえば、x++を渡す、マクロが複数回インクリメントする可能性がある)であるため、これに注意してください

引数の複数のインスタンス化による副作用を回避するマクロを作成できます。

C11ジェネリック

さまざまなタイプで動作し、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関数を使用できることです。

11
ideasman42

パラメーターとコードの型チェックが繰り返されないため、コードが膨張する可能性があります。マクロ構文は、セミコロンまたは優先順位が邪魔になる可能性のある奇妙なEdgeのケースをいくつも引き起こす可能性があります。ここにいくつかのマクロを示すリンクがあります evil

11
Michael Dorgan

マクロの1つの欠点は、デバッガーが展開されたマクロを持たないソースコードを読み取るため、マクロでデバッガーを実行することが必ずしも有用ではないことです。言うまでもなく、関数の場合のようにマクロ内にブレークポイントを設定することはできません。

6
jim mcnamara

この答えに追加する..

マクロは、プリプロセッサによってプログラムに直接置き換えられます(基本的にプリプロセッサディレクティブであるため)。したがって、それらは必然的にそれぞれの関数よりも多くのメモリ空間を使用します。一方、関数は呼び出されて結果を返すためにより多くの時間を必要とし、マクロを使用することでこのオーバーヘッドを回避できます。

また、マクロには、異なるプラットフォームでのプログラムの移植性を支援できる特別なツールがいくつかあります。

関数とは異なり、マクロの引数にデータ型を割り当てる必要はありません。

全体として、それらはプログラミングに役立つツールです。また、状況に応じて、マクロ命令と関数の両方を使用できます。

6
Nikos

関数は型チェックを行います。これにより、安全性がさらに高まります。

5
ncmathsadist

上記の答えでは、マクロよりも関数の利点の1つが非常に重要だとは思いませんでした。

関数は引数として渡すことができますが、マクロは渡すことができません。

具体的な例:別の文字列内で検索する明示的な文字リストではなく、受け入れる「strpbrk」関数の代替バージョンを記述したい場合、a(ポインタ)テスト(ユーザー定義)に合格する文字が見つかるまで0を返す関数。これを行う理由の1つは、他の標準ライブラリ関数を利用できるようにするためです。句読点でいっぱいの明示的な文字列を提供する代わりに、代わりにctype.hの「ispunct」などを渡すことができます。マクロ、これは機能しません。

他にもたくさんの例があります。たとえば、関数ではなくマクロで比較を行う場合、stdlib.hの「qsort」に渡すことはできません。

Pythonの類似した状況は、バージョン2とバージョン3での「印刷」です(通過不可能なステートメントと通過可能な関数)。

2
Sean Rostami

マクロに引数として関数を渡すと、毎回評価されます。たとえば、最も人気のあるマクロの1つを呼び出す場合:

#define MIN(a,b) ((a)<(b) ? (a) : (b))

そのように

int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));

functionThatTakeLongTimeは5回評価され、パフォーマンスが大幅に低下する可能性があります

1
Saffer