教授からのクイズの回答を調べていますが、質問は次のとおりです。
絶対値のマクロのような関数の正しい実装は次のとおりです。
#define abs(x) ((x)<0 ? (-x) : (x))
#define abs(x) ((x)<0 ? -(x) : (x))
なぜ2番目のものが最初のものに対して正しいのですか?
そして、なぜすべての()を使用する必要があるのですか。関係するルールは何ですか?すべての変数には()が必要ですか?ありがとう。
余分な括弧が解決するさまざまな関連する問題があります。私はそれらを一つずつ見ていきます:
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
_
はい、すべての変数には直接括弧が必要です。
その理由は、算術式や実際には単一の変数ではない式のように、「ニース」ではないものをマクロに渡すことができるためです。 abs(1+2)
を使用すると、展開された-(1 + 2)
が_(-1 + 2)
_とは異なる結果をもたらすことが簡単にわかります。これが、-(x)
がより正確である理由です。
残念ながら、どちらのマクロも安全ではありません。代わりに、このようなものにはインライン関数を使用する必要があります。考えてみましょう:
_abs (x++); // expands to ((x++) < 0 ? - (x++) : (x++))
_
これはマクロでは明らかに間違っていますが、代わりにインライン関数を使用すると正しく機能します。
関数の代わりにマクロを使用することには、他にも問題があります。 この質問 を参照してください。
編集:質問はCに関するものであることに注意してください。インライン関数は、C99でのみ使用できる場合があります。