web-dev-qa-db-ja.com

プリプロセッサマクロはなぜ悪であり、代替手段は何ですか?

私はいつもこれを尋ねましたが、本当に良い答えを受け取ったことはありません。私は、最初の「Hello World」を書く前であっても、ほとんどのプログラマーが「マクロは絶対に使用しないでください」、「マクロは悪」などのフレーズに遭遇したと思います。新しいC++ 11では、何年も後に本当の選択肢がありますか?

簡単な部分は、#pragmaのような、プラットフォーム固有およびコンパイラ固有のマクロについてです。ほとんどの場合、少なくとも2つの重要な状況でエラーが発生しやすい#pragma onceのような重大な欠陥があります。さまざまなパスといくつかのネットワーク設定とファイルシステム。

しかし、一般的には、マクロとその使用方法の代替品についてはどうでしょうか?

81
user1849534

マクロは他のツールと同じです-殺人で使用されるハンマーはハンマーなので悪ではありません。人がそのように使うのは悪です。釘を打ちたい場合、ハンマーは完璧なツールです。

マクロには「悪い」といういくつかの側面があります(後で詳しく説明し、代替案を提案します)。

  1. マクロをデバッグすることはできません。
  2. マクロ展開は、奇妙な副作用を引き起こす可能性があります。
  3. マクロには「名前空間」がないため、他の場所で使用されている名前と衝突するマクロがある場合、必要のない場所でマクロを置換することになり、通常は奇妙なエラーメッセージが表示されます。
  4. マクロは、あなたが気付いていないことに影響を与えるかもしれません。

ここで少し拡大しましょう。

1)マクロはデバッグできません。数値または文字列に変換するマクロがある場合、ソースコードにはマクロ名があり、多くのデバッガーは「表示」できません。マクロの翻訳先。そのため、実際に何が起こっているのかわかりません。

交換enumまたは_const T_を使用

「関数のような」マクロの場合、デバッガは「現在のソース行ごと」レベルで動作するため、マクロは1つのステートメントでも100のステートメントでも、単一のステートメントのように動作します。何が起こっているのか把握するのが難しくなります。

Replacement:関数を使用する-「高速」にする必要がある場合はインライン(ただし、インラインが多すぎるのは良いことではないことに注意してください)

2)マクロ展開には奇妙な副作用があります。

有名なのは#define SQUARE(x) ((x) * (x))x2 = SQUARE(x++)の使用です。これはx2 = (x++) * (x++);につながります。これは、たとえ有効なコード[1]であったとしても、プログラマーが望んでいたものとはほぼ間違いないでしょう。関数である場合、x ++を実行しても問題はなく、xは1回だけ増加します。

別の例は、マクロ内の「if else」です。これがあるとします。

_#define safe_divide(res, x, y)   if (y != 0) res = x/y;
_

その後

_if (something) safe_divide(b, a, x);
else printf("Something is not set...");
_

実際には完全に間違ったものになります。

置換:実関数。

)マクロには名前空間がありません

マクロがある場合:

_#define begin() x = 0
_

そして、beginを使用するC++のコードがいくつかあります。

_std::vector<int> v;

... stuff is loaded into v ... 

for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
   std::cout << ' ' << *it;
_

さて、あなたはどのエラーメッセージを受け取ると思いますか、そして他の誰かが書いたヘッダーファイルにあるbeginマクロを完全に忘れた、あるいは知らなかったと仮定してどこでエラーを探しますか? [そして、インクルードの前にそのマクロをインクルードした場合、さらに楽しい-コード自体を見たときにまったく意味をなさない奇妙なエラーにdrれているでしょう。

Replacement:「ルール」ほどの置換はありません-マクロには大文字の名前のみを使用し、他のすべてに大文字の名前を使用しないでください。

4)マクロには気づかない効果がある

この関数を使用します。

_#define begin() x = 0
#define end() x = 17
... a few thousand lines of stuff here ... 
void dostuff()
{
    int x = 7;

    begin();

    ... more code using x ... 

    printf("x=%d\n", x);

    end();

}
_

さて、マクロを見なくても、beginは関数であり、xには影響しないと考えるでしょう。

この種のこと、そしてもっと複雑な例を見てきましたが、本当にあなたの一日を台無しにすることができます!

置換:マクロを使用してxを設定しないか、xを引数として渡します。

マクロを使用することが有益な場合があります。 1つの例は、ファイル/行情報を渡すマクロで関数をラップすることです。

_#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x)  my_debug_free(x, __FILE__, __LINE__)
_

これで_my_debug_malloc_をコード内の通常のmallocとして使用できますが、追加の引数があるため、最後になって「どのメモリ要素が解放されていないか」をスキャンすると、プログラマーがリークを追跡できるように割り当てが行われました。

[1] 1つの変数を「シーケンスポイントで」複数回更新することは、未定義の動作です。シーケンスポイントはステートメントとまったく同じではありませんが、ほとんどの意図と目的のために、それを考慮すべきです。したがって、_x++ * x++_を実行するとxが2回更新されますが、これは未定義であり、おそらく異なるシステムで異なる値になり、xでも異なる結果値になります。

141
Mats Petersson

「マクロは悪」という言葉は、通常、#pragmaではなく#defineの使用を指します。

具体的には、式は次の2つのケースを参照します。

  • マジックナンバーをマクロとして定義する

  • マクロを使用して式を置き換える

新しいC++ 11では、何年も後に本当の選択肢がありますか?

はい、上記のリストの項目について(マジックナンバーはconst/constexprで定義し、式は[normal/inline/template/inline template]関数で定義する必要があります。

以下は、マジックナンバーをマクロとして定義し、式をマクロで置き換えることによって導入される問題の一部です(これらの式を評価する関数を定義する代わりに):

  • マジックナンバーのマクロを定義する場合、コンパイラは定義された値の型情報を保持しません。これにより、コンパイルの警告(およびエラー)が発生し、コードのデバッグを混乱させる可能性があります。

  • 関数の代わりにマクロを定義する場合、そのコードを使用するプログラマは、それらが関数のように機能することを期待しますが、そうではありません。

次のコードを検討してください。

_#define max(a, b) ( ((a) > (b)) ? (a) : (b) )

int a = 5;
int b = 4;

int c = max(++a, b);
_

Cへの代入後は、aとcが6になると予想されます(マクロの代わりにstd :: maxを使用した場合)。代わりに、コードは以下を実行します。

_int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7
_

さらに、マクロは名前空間をサポートしていません。つまり、コードでマクロを定義すると、使用できる名前のクライアントコードが制限されます。

これは、上記のマクロを定義すると(maxの場合)、明示的に記述しない限り、以下のコードで_#include <algorithm>_を使用できなくなることを意味します。

_#ifdef max
#undef max
#endif
#include <algorithm>
_

変数/関数の代わりにマクロを使用することは、アドレスを取得できないことも意味します。

  • 定数としてのマクロがマジック番号に評価される場合、アドレスで渡すことはできません

  • 関数としてのマクロの場合、述部として使用したり、関数のアドレスを取得したり、ファンクターとして扱うことはできません。

編集:例として、上記の_#define max_の正しい代替案:

_template<typename T>
inline T max(const T& a, const T& b)
{
    return a > b ? a : b;
}
_

これにより、マクロのすべてが実行されますが、1つの制限があります。引数のタイプが異なる場合、テンプレートバージョンは明示的に強制します(実際には、より安全で明示的なコードになります)。

_int a = 0;
double b = 1.;
max(a, b);
_

この最大値がマクロとして定義されている場合、コードはコンパイルされます(警告付き)。

このmaxがテンプレート関数として定義されている場合、コンパイラはあいまいさを指摘し、max<int>(a, b)またはmax<double>(a, b)のいずれかを言う必要があります(したがって、明示的に意図を述べます)。

21
utnapistim

よくあるトラブルはこれです:

#define DIV(a,b) a / b

printf("25 / (3+2) = %d", DIV(25,3+2));

プリプロセッサは次のように展開するため、5ではなく10を出力します。

printf("25 / (3+2) = %d", 25 / 3 + 2);

このバージョンはより安全です:

#define DIV(a,b) (a) / (b)
11
phaazon

マクロは、一般的なパラメーター(マクロのパラメーターは何でもかまいません)、時にはパラメーターを使用して作成する場合に特に役立ちます。

さらに、このコードは、マクロが使用されるポイントに配置されます(つまり、挿入されます)。

OTOH、同様の結果が達成される可能性があります:

  • オーバーロードされた関数(異なるパラメータータイプ)

  • c ++のテンプレート(一般的なパラメーターのタイプと値)

  • インライン関数(シングルポイント定義にジャンプする代わりに、それらが呼び出される場所にコードを配置します。ただし、これはむしろコンパイラの推奨事項です)。

編集:マクロが悪い理由は:

1)引数の型チェックがない(型がない)ため、簡単に誤用される可能性がある-次のようなマクロ内のコードを起こしやすい

#define MULTIPLY(a,b) a*b

そして、呼び出します

MULTIPLY(2+3,4+5)

で展開する

2 + 3 * 4 + 5(および(2 + 3)*(4 + 5)ではない)。

後者を使用するには、以下を定義する必要があります。

#define MULTIPLY(a,b) ((a)*(b))
3
user1284631

プリプロセッサ定義またはマクロを呼び出すときに使用することに問題はないと思います。

これらはc/c ++に見られる(メタ)言語の概念であり、他のツールと同様に、自分が何をしているのかを知っていればあなたの生活を楽にすることができます。マクロの問題は、c/c ++コードの前にマクロが処理され、障害が発生し、ほとんど明らかなコンパイラエラーを引き起こす可能性がある新しいコードを生成することです。明るい面では、コードをきれいに保ち、適切に使用すれば大量の入力を節約できるので、個人的な好みになります。

3
Sandi Hrvić

C/C++のマクロは、バージョン管理の重要なツールとして機能します。マクロのマイナー構成を使用して、同じコードを2つのクライアントに配信できます。私のようなものを使用します

#define IBM_AS_CLIENT
#ifdef IBM_AS_CLIENT 
  #define SOME_VALUE1 X
  #define SOME_VALUE2 Y
#else
  #define SOME_VALUE1 P
  #define SOME_VALUE2 Q
#endif

この種の機能は、マクロなしではそれほど簡単に実現できません。マクロは実際には優れたソフトウェア構成管理ツールであり、コードを再利用するためのショートカットを作成するだけの方法ではありません。マクロで再利用を目的として関数を定義すると、間違いなく問題が発生する可能性があります。

1
indiangarg

問題は、マクロがコンパイラによって十分に最適化されておらず、読み取りとデバッグが「見苦しい」ことだと思います。

多くの場合、適切な代替手段は汎用関数やインライン関数です。

1
Davide Icardi

プリプロセッサマクロは、次のような意図された目的で使用される場合、悪ではありません。

  • #ifdefタイプのコンストラクトを使用して同じソフトウェアの異なるリリースを作成する(たとえば、異なる地域のウィンドウのリリース)。
  • 関連する値をテストするコードを定義します。

Alternatives- ini、xml、json形式の構成ファイルを同様の目的で使用できます。しかし、それらを使用すると、プリプロセッサマクロで回避できるコードに実行時の影響があります。

0
indiangarg