【C言語】
if(a||b&&c)が【&&】で改行してたら怪しい

if(a||b&&c)に括弧を付けて

warning: suggest parentheses around ‘&&’ within ‘||’

警告:A||B&&CはA||(B&&C)と同じで(A||B)&&Cと違う
[-Wparentheses]
[-Wlogical-op-parentheses]


■改行位置から推測される結合

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

参考:

(year) %4 == 0 && ((year) %100 != 0 || (year) %400 == 0)

EXP02-C. 論理 AND 演算子および論理 OR 演算子のショートサーキット動作について注意する


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()の評価を行わないのが
わかると思います。