warning: suggest parentheses around ‘&&’ within ‘||’
警告:A||B&&CはA||(B&&C)と同じで(A||B)&&Cと違う
[-Wparentheses]
[-Wlogical-op-parentheses]
■この記事の概要
この記事では、C言語におけるif
文で論理演算子「||
」と「&&
」を併用した際の結合順序の問題を解説しています。プログラマが意図した結合とコンパイラの解釈が異なる場合、誤動作を防ぐために括弧を使用して明示する重要性を強調。具体例や評価順序の仕組みを示し、バグ回避のポイントを伝えます。
■改行位置から推測される結合
int f1(int a,int b,int c)
{
if(a && //ダメ:改行位置から推測される結合が違う
b || c) {
return 1;
}
return 0;
}
(a&&b||c)は
((a&&b)||c) と同じで
(a&&(b||c))と違います。
この関数では改行位置から推測される
プログラマが期待する結合力と
コンパイラが解釈する結合力が違います。
改行の位置で結合力は変わりません。
丸カッコ()を使って結合力を明確にしましょう。
■プログラマが期待する結合
int f2(int a,int b,int c)
{
if( a &&
(b || c) ) {//プログラマが期待する結合???
return 1;
}
return 0;
}
改行位置から推測すると
プログラマは(b||c)で結合する事を期待しているようです。
■コンパイラが解釈する結合
int f3(int a,int b,int c)
{
if((a && b) || //コンパイラはこう解釈する
c) {
return 1;
}
return 0;
}
コンパイラの解釈はプログラマの期待と違い(a&&b)で結合します。
■閏年の判定式と括弧の位置
閏年の判定式のカッコはどちらに付けるのが適切でしょうか?
if(Y % 4 == 0 && Y % 100 != 0 || Y % 400 == 0)は
if((Y % 4 == 0 && Y % 100 != 0) || Y % 400 == 0)?
if(Y % 4 == 0 && (Y % 100 != 0 || Y % 400 == 0))?
ちょっと意外ですがどちらでも可。
どちらにカッコを付けても同じ結果になります。
しかし次に示すように
いつでも同じ結果になるわけではないので
カッコつけましょう。
■A||(B&&C)と(A||B)&&Cが違う例
#include <stdio.h>
#include <stdbool.h>
void diff(bool x,bool y,bool z){
bool e1 = (x||y)&&z ;
bool e2 = x||(y&&z) ;
if(e1 != e2){
printf("diff:(%d|| %d)&&%d = %d\n",x,y,z,e1);
printf("diff: %d||(%d &&%d)= %d\n",x,y,z,e2);
}else{
printf("same:(%d|| %d)&&%d = %d\n",x,y,z,e1);
printf("same: %d||(%d &&%d)= %d\n",x,y,z,e2);
}
}
int main(void){
diff(0,0,0);
diff(0,0,1);
diff(0,1,0);
diff(0,1,1);
diff(1,0,0);
diff(1,0,1);
diff(1,1,0);
diff(1,1,1);
}
➡実行結果
./a.out
same:(0|| 0)&&0 = 0
same: 0||(0 &&0)= 0
same:(0|| 0)&&1 = 0
same: 0||(0 &&1)= 0
same:(0|| 1)&&0 = 0
same: 0||(1 &&0)= 0
same:(0|| 1)&&1 = 1
same: 0||(1 &&1)= 1
diff:(1|| 0)&&0 = 0
diff: 1||(0 &&0)= 1
same:(1|| 0)&&1 = 1
same: 1||(0 &&1)= 1
diff:(1|| 1)&&0 = 0
diff: 1||(1 &&0)= 1
same:(1|| 1)&&1 = 1
same: 1||(1 &&1)= 1
参考:
■a||(b&&c)でbが先に評価されるわけではない
#include <stdio.h>
#include <stdbool.h>
int a(bool x){ printf("%s()->",__func__); return x;}
int b(bool x){ printf("%s()->",__func__); return x;}
int c(bool x){ printf("%s()->",__func__); return x;}
int main(void){
if(a(0)||b(1)&&c(0))
puts("0||1&&0=真");
else
puts("0||1&&0=偽");
//a||b&&c の評価はどれが先?
#define LOG(X) (printf("%s:",#X),printf("%s\n",(X)?"真":"偽"))
LOG(a(0)||b(0)&&c(0));
LOG(a(0)||b(0)&&c(1));
LOG(a(0)||b(1)&&c(0));
LOG(a(0)||b(1)&&c(1));
LOG(a(1)||b(0)&&c(0));
LOG(a(1)||b(0)&&c(1));
LOG(a(1)||b(1)&&c(0));
LOG(a(1)||b(1)&&c(1));
}
||と&&では&&のほうが結合力が強いです。
なので
a||b&&ccは
a||(b&&c)と同じです。
結合力が強いからといって
(b&&c)が先に評価されるわけではありません。
論理式は左から右へ順番に評価されます。
つまり、必ずaから評価されます。
評価の途中で結果が出たら
以降の評価はされません。
➡実行結果
a()->b()->c()->0||1&&0=偽
a(0)||b(0)&&c(0):a()->b()->偽
a(0)||b(0)&&c(1):a()->b()->偽
a(0)||b(1)&&c(0):a()->b()->c()->偽
a(0)||b(1)&&c(1):a()->b()->c()->真
a(1)||b(0)&&c(0):a()->真
a(1)||b(0)&&c(1):a()->真
a(1)||b(1)&&c(0):a()->真
a(1)||b(1)&&c(1):a()->真
かならずa()から評価を行い、
a(1)が成立すると
b(),c()の評価を行わないのが
わかると思います。