Search code examples
c++operatorsevaluationshort-circuitingparentheses

short circuiting and parenthesis


Does it matter how I group subexpressions when dealing with a single short-circuiting operator?

a && b && c && d
a && (b && (c && d))
(a && b) && (c && d)
((a && b) && c) && d

Are the above expressions equivalent?


Solution

  • It is relatively easy to prove the equivalence for two simplified cases of three subexpressions:

    a && (b && c)  -->  a && bc   // bc is a shorthand for b && c
    

    Here, a will be evaluated first. If it is false, short circuiting will prevent the evaluation of bc. If it is true, bc will be evaluated, that is, b && c will be evaluated. If b is false, c won't be evaluated.

    (a && b) && c  -->  ab && c   // ab is a shorthand for a && b
    

    Here, ab will be evaluated first. (That is a && b is evaluated first. If a is false, short circuiting will prevent the evaluation of b. Otherwise, ab yields b.) If ab is false, c won't be evaluated.


    Now, if you prefer evidence to proof, you can look at the assembly output of the following C code:

    int a(), b(), c(), d();
    
    void e()
    {
        a() && b() && c() && d();
    }
    
    void f()
    {
        a() && (b() && (c() && d()));
    }
    
    void g()
    {
        (a() && b()) && (c() && d());
    }
    
    void h()
    {
        ((a() && b()) && c()) && d();
    }
    

    (I used C code as opposed to C++ code to prevent name mangling.)

    generated assembly for e:

    _e:
        // ... enter ...
        call    _a
        testl   %eax, %eax
        je  L1
        call    _b
        testl   %eax, %eax
        je  L1
        call    _c
        testl   %eax, %eax
        je  L1
        call    _d
        testl   %eax, %eax
        nop
    L1:
        // ... leave ...
    

    generated assembly for f:

    _f:
        // ... enter ...
        call    _a
        testl   %eax, %eax
        je  L4
        call    _b
        testl   %eax, %eax
        je  L4
        call    _c
        testl   %eax, %eax
        je  L4
        call    _d
        testl   %eax, %eax
        nop
    L4:
        // ... leave ...
    

    generated assembly for g:

    _g:
        // ... enter ...
        call    _a
        testl   %eax, %eax
        je  L7
        call    _b
        testl   %eax, %eax
        je  L7
        call    _c
        testl   %eax, %eax
        je  L7
        call    _d
        testl   %eax, %eax
        nop
    L7:
        // ... leave ...
    

    generated assembly for h:

    _h:
        // ... enter ...
        call    _a
        testl   %eax, %eax
        je  L10
        call    _b
        testl   %eax, %eax
        je  L10
        call    _c
        testl   %eax, %eax
        je  L10
        call    _d
        testl   %eax, %eax
        nop
    L10:
        // ... leave ...
    

    As you can see, apart from labels, the generated assembly code is completely identical.