web-dev-qa-db-ja.com

Cマクロの質問-(x)vs(-x)

教授からのクイズの回答を調べていますが、質問は次のとおりです。

絶対値のマクロのような関数の正しい実装は次のとおりです。

#define abs(x) ((x)<0 ? (-x) : (x))
#define abs(x) ((x)<0 ? -(x) : (x))

なぜ2番目のものが最初のものに対して正しいのですか?

そして、なぜすべての()を使用する必要があるのですか。関係するルールは何ですか?すべての変数には()が必要ですか?ありがとう。

22
Crystal

余分な括弧が解決するさまざまな関連する問題があります。私はそれらを一つずつ見ていきます:

試してください:int y = abs( a ) + 2

あなたが使用すると仮定しましょう:

_#define abs(x)  (x<0)?-x:x
...
    int y = abs( a ) + 2
_

これはint y = (a<0)?-a:a+2に展開されます。 _+2_は、誤った結果にのみバインドします。 2は、aが正の場合にのみ追加され、負の場合には追加されません。したがって、全体を括弧で囲む必要があります。

_#define abs(x)  ( (x<0) ? -x : x )
_

試してください:int y = abs(a+b);

しかし、int y = abs(a+b)に展開されるint y = ( (a+b<0) ? -a+b : a+b)があるかもしれません。 a + bが負の場合、結果を加算するときにbは否定されません。したがって、_-x_のxを括弧で囲む必要があります。

_#define abs(x)  ( (x<0) ? -(x) : x )
_

試してください:int y = abs(a=b);

これは合法であるはずですが(悪いですが)、int y = ( (a=b<0)?-(a=b):a=b );に展開され、最後のbを3値に割り当てようとします。これはコンパイルされるべきではありません。 (C++で動作することに注意してください。「代入の無効な左辺値」エラーでコンパイルに失敗するのを確認するためにg ++ではなくgccでコンパイルする必要がありました。)

_#define abs(x)  ( (x<0) ? -(x) : (x) )
_

試してください:int y = abs((a<b)?a:b);

これはint y = ( ((a<b)?a:b<0) ? -((a<b)?a:b) : (a<b)?a:b )に展開され、意図したように3項全体ではなく、_<0_をbでグループ化します。

_#define abs(x)  ( ( (x) < 0) ? -(x) : (x) )
_

結局、xの各インスタンスは、解決するために括弧が必要なグループ化の問題を起こしがちです。

一般的な問題:演算子の優先順位

これらすべてに共通するスレッドは 演算子の優先順位abs(...)の呼び出しに、優先順位の低い演算子を配置すると、xが使用されている場所の周辺になります。マクロの場合、正しくバインドされません。たとえば、abs(a=b)は_a=b<0_に展開されます。これはa=(b<0)...と同じであり、呼び出し元が意味するものではありません。

absを実装するための「正しい方法」

もちろん、これはとにかくabsを実装する間違った方法です...組み込み関数を使用したくない場合(移植先のハードウェアに合わせて最適化されるため、使用する必要があります)、 Meyers、 Sutter などがmin関数とmax関数の再実装について説明しているのと同じ理由で、インラインテンプレート(C++を使用している場合)。 (他の回答もそれについて言及しています:abs(x++)はどうなりますか?)

私の頭から離れて、合理的な実装は次のようになります。

_template<typename T> inline const T abs(T const & x)
{
    return ( x<0 ) ? -x : x;
}
_

ここでは、xは単一の値であり、マクロからの任意の展開ではないことがわかっているため、括弧を省略してもかまいません。

さらに良いことに、Chris Lutzが以下のコメントで指摘しているように、テンプレートの特殊化を使用して、最適化されたバージョン(abs、fabs、labs)を呼び出し、型の安全性、非組み込み型のサポート、およびパフォーマンスのすべての利点を得ることができます。

テストコード

_#if 0
gcc $0 -g -ansi -std=c99 -o exe && ./exe
exit
#endif




#include <stdio.h>

#define abs1(x)  (x<0)?-x:x
#define abs2(x)  ((x<0)?-x:x)
#define abs3(x)  ((x<0)?-(x):x)
#define abs4(x)  ((x<0)?-(x):(x))
#define abs5(x)  (((x)<0)?-(x):(x))


#define test(x)     printf("//%30s=%d\n", #x, x);
#define testt(t,x)  printf("//%15s%15s=%d\n", t, #x, x);

int main()
{
    test(abs1( 1)+2)
    test(abs1(-1)+2)
    //                    abs1( 1)+2=3
    //                    abs1(-1)+2=1

    test(abs2( 1+2))
    test(abs2(-1-2))
    //                    abs2( 1+2)=3
    //                    abs2(-1-2)=-1

    int a,b;
    //b =  1; testt("b= 1; ", abs3(a=b))
    //b = -1; testt("b=-1; ", abs3(a=b))
    // When compiled with -ansi -std=c99 options, this gives the errors:
    //./so1a.c: In function 'main':
    //./so1a.c:34: error: invalid lvalue in assignment
    //./so1a.c:35: error: invalid lvalue in assignment

    // Abs of the smaller of a and b. Should be one or two.
    a=1; b=2; testt("a=1; b=2; ", abs4((a<b)?a:b))
    a=2; b=1; testt("a=2; b=1; ", abs4((a<b)?a:b))
    //               abs4((a<b)?a:b)=-1
    //               abs4((a<b)?a:b)=1


    test(abs5( 1)+2)
    test(abs5(-1)+2)
    test(abs5( 1+2))
    test(abs5(-1-2))
    b =  1; testt("b= 1; ", abs5(a=b))
    b = -1; testt("b=-1; ", abs5(a=b))
    a=1; b=2; testt("a=1; b=2; ", abs5((a<b)?a:b))
    a=2; b=1; testt("a=2; b=1; ", abs5((a<b)?a:b))
}
_

出力

_                    abs1( 1)+2=3
                    abs1(-1)+2=1
                    abs2( 1+2)=3
                    abs2(-1-2)=-1
     a=1; b=2; abs4((a<b)?a:b)=-1
     a=2; b=1; abs4((a<b)?a:b)=1
                    abs5( 1)+2=3
                    abs5(-1)+2=3
                    abs5( 1+2)=3
                    abs5(-1-2)=3
         b= 1;       abs5(a=b)=1
         b=-1;       abs5(a=b)=1
     a=1; b=2; abs5((a<b)?a:b)=1
     a=2; b=1; abs5((a<b)?a:b)=1
_
30
markets

はい、すべての変数には直接括弧が必要です。

その理由は、算術式や実際には単一の変数ではない式のように、「ニース」ではないものをマクロに渡すことができるためです。 abs(1+2)を使用すると、展開された-(1 + 2)が_(-1 + 2)_とは異なる結果をもたらすことが簡単にわかります。これが、-(x)がより正確である理由です。

残念ながら、どちらのマクロも安全ではありません。代わりに、このようなものにはインライン関数を使用する必要があります。考えてみましょう:

_abs (x++); // expands to ((x++) < 0 ? - (x++) : (x++))
_

これはマクロでは明らかに間違っていますが、代わりにインライン関数を使用すると正しく機能します。

関数の代わりにマクロを使用することには、他にも問題があります。 この質問 を参照してください。

編集:質問はCに関するものであることに注意してください。インライン関数は、C99でのみ使用できる場合があります。

14
Dan Olson