开发者

short circuiting and parenthesis

开发者 https://www.devze.com 2023-03-31 02:47 出处:网络
Does it matter how I group subexpressions when dealing with a single short-circuiting operator? a && b && c && d

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

a && b && c && d
a && (b && (c && d))开发者_StackOverflow
(a && b) && (c && d)
((a && b) && c) && d

Are the above expressions equivalent?


Yes, those expressions are all equivalent, including their short-circuiting behaviour.

The parentheses change the order in which the individual &&s are evaluated. However, as && is always left-associative, the terms are always evaluated in a left-to-right order. So as soon as a term is found to be false, the rest can be skipped.


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.


In your example, the parenthesis don't matter. But that's just becuase of the nature of && where all the terms need to be checked (if true, or if either one is false it is false).

In this example, the parenthesis do make a big difference:

(a && b) || (c && d) // either a & b are true, or c & d

a && (b || c && d) // a must be true, and either b or c & d

(a && b || c) && d // d must be true, and either c or a & b

And of course because the logic is different, the short-circuiting works differently.
In the first line if a is false, it will continue to the second term (c && d).
In the second line if a is false, it will just return false.


This property is called Associativity. From the Wikipedia Article:

In mathematics, associativity is a property of some binary operations. It means that, within an expression containing two or more occurrences in a row of the same associative operator, the order in which the operations are performed does not matter as long as the sequence of the operands is not changed. That is, rearranging the parentheses in such an expression will not change its value.

The built-in operator&& is fully associative, and thus the above applies.

This is not always the case, for example:

  • operator- is generally left-associative, that is a - b - c == (a - b) - c != a - (b - c)
  • exponentiation is right-associative, that is a ** b ** c == a ** (b ** c) != (a ** b) ** c
  • cross-product is non-associative, that is (a x b) x c != a x (b x c) (and without parentheses, the expression does not even makes sense)

Note that this only applies to the case of a single operator being used consistently, as soon as another operator (like ||) is introduced in the mix, then you have to take into account the operator precedence, which is another topic.

0

精彩评论

暂无评论...
验证码 换一张
取 消