web-dev-qa-db-ja.com

Cのマクロでの括弧の必要性

次のコードでマクロSQRの定義を試してみました。

_#define SQR(x) (x*x)
int main()
{
    int a, b=3;
    a = SQR(b+5);      // Ideally should be replaced with (3+5*5+3), though not sure.
    printf("%d\n",a);
    return 0;
}
_

_23_を出力します。マクロ定義をSQR(x) ((x)*(x))に変更すると、出力は期待どおりの_64_になります。 Cでマクロを呼び出すと、その呼び出しがマクロの定義で置き換えられることはわかっていますが、それでも_23_の計算方法を理解できません。

31
Kushal

プリプロセッサマクロは、コードがコンパイルされる前にテキスト置換を実行するため、SQR(b+5)は(b + 5 * b + 5)=(6b + 5)= 6 * 3 + 5 = 23に変換されます

通常の関数呼び出しは、関数に渡す前にパラメーター(b + 3)の値を計算しますが、マクロは事前にコンパイルされた置換であるため、演算の代数的順序が非常に重要になります。

30
Babak Naffas

(3+5*3+5 == 23)

一方、((3+5)*(3+5)) == 64

これを行う最良の方法はマクロを使用しないです。

inline int SQR(int x) { return x*x; }

または、単にx*x

10
Luchian Grigore

このマクロを使用したマクロの置換を検討してください。

_#define SQR(x) (x*x)
_

引数として_b+5_を使用します。自分で交換してください。コードでは、SQR(b+5)は_(b+5*b+5)_または_(3+5*3+5)_になります。 演算子の優先順位の規則を覚えておいてください:_*_の前の_+_。したがって、これは_(3+15+5)_または_23_として評価されます。

マクロの2番目のバージョン:

_#define SQR(x) ((x) * (x))
_

正しいのは、括弧を使用して演算子の優先順位の影響からマクロ引数を保護しているためです。

このページ Cのオペレーター設定の説明には、Niceチャートがあります。 こちら C11リファレンスドキュメントの関連セクション。

ここで覚えておくべきことは、括弧を使用して、マクロ内の引数を常にシールドする習慣を身に付けるべきであることです。

9
pb2q

マクロは次のように展開されます

 a = b+5*b+5;

つまり.

 a = b + (5*b) + 5;

だから23。

6
Mat

前処理後、SQR(b+5)は_(b+5*b+5)_に展開されます。これは明らかに正しくありません。

SQRの定義には2つの一般的なエラーがあります。

  1. マクロの引数をマクロ本体の括弧で囲まないでください。これらの引数が式である場合、それらの式で優先順位が異なる演算子が問題を引き起こす可能性があります。これはこの問題を修正したバージョンです

    _#define SQR(x) ((x)*(x))
    _
  2. マクロの引数を複数回評価するため、それらの引数が副作用のある式である場合、それらの副作用は複数回発生する可能性があります。たとえば、SQR(++x)の結果を考えてみましょう。

    GCC typeof 拡張子を使用すると、この問題は次のように修正できます

    _#define SQR(x) ({ typeof (x) _x = (x); _x * _x; })
    _

これらの問題はどちらも、マクロをインライン関数に置き換えることで修正できます

_   inline int SQR(x) { return x * x; }
_

これにはGCCインライン拡張またはC99が必要です。 6.40インライン関数はマクロと同じくらい高速です を参照してください。

4
Lee Duhem

マクロは単なるテキスト置換です。前処理後のコードは次のようになります。

int main()
{
    int a, b=3;
    a = b+5*b+5;
    printf("%d\n",a);
    return 0;
}

乗算は加算よりも演算子の優先順位が高いため、aの値を計算するとき、2つの加算の前に行われます。マクロ定義に括弧を追加すると、次のようにして問題を修正できます。

int main()
{
    int a, b=3;
    a = (b+5)*(b+5);
    printf("%d\n",a);
    return 0;
}

括弧で囲まれた演算は乗算の前に評価されるため、加算が最初に行われ、a = 64あなたが期待する結果。

2
Caleb

マクロは単なる文字列置換であり、完了プロセスの前に発生するためです。コンパイラーは、マクロ変数とその値を確認する機会がありません。例:マクロが次のように定義されている場合

#define BAD_SQUARE(x)  x * x 

このように呼ばれた

BAD_SQUARE(2+1) 

コンパイラはこれを見ます

2 + 1 * 2 + 1

予期せぬ結果をもたらす可能性があります

5

この動作を修正するには、次のように、マクロ変数を常に括弧で囲む必要があります。

#define GOOD_SQUARE(x)  (x) * (x) 

このマクロが呼び出されたとき、たとえば、このように

GOOD_SQUARE(2+1)

コンパイラはこれを見ます

(2 + 1) * (2 + 1)

その結果

9

さらに、これはポイントをさらに説明するための完全な例です

#include <stdio.h>

#define BAD_SQUARE(x)  x * x 
// In macros alsways srround the variables with parenthesis
#define GOOD_SQUARE(x)  (x) * (x) 

int main(int argc, char const *argv[])
{
    printf("BAD_SQUARE(2) = : %d \n", BAD_SQUARE(2) ); 
    printf("GOOD_SQUARE(2) = : %d \n", GOOD_SQUARE(2) ); 
    printf("BAD_SQUARE(2+1) = : %d ; because the macro will be \
subsituted as 2 + 1 * 2 + 1 \n", BAD_SQUARE(2+1) ); 
    printf("GOOD_SQUARE(2+1) = : %d ; because the macro will be \
subsituted as (2 + 1) * (2 + 1) \n", GOOD_SQUARE(2+1) ); 

    return 0;
}
0
Amjad