4

Today I learned about eager boolean evaluation. In the following example, Bar() will be evaluated even when Foo() is true.

if (Foo() | Bar())

This answer on SO has an example:

if ((first = (i == 7)) & (second = (j == 10))) { //do something }

The use-case here would be that you want to reuse the results, but I would rather write it like this then:

first = (i == 7);
second = (j == 10);
if (first && second) { //do something }

Another use-case would be that Bar() has a side-effect that should be executed regardless of Foo() being true or false. Question is, can that be good code or would it indicate a code smell?

4
  • 3
    Like you noticed, assignment in an if, regardless of the eagerness of the evaluation, is usually a bad practice. So that's not an argument in favor of bitwise operators.
    – Andres F.
    Sep 19, 2013 at 13:32
  • 2
    Also note that, unlike || and &&, with this style it's undetermined which of Foo() and Bar() will be evaluated first. See also stackoverflow.com/questions/3962068/… Sep 19, 2013 at 14:04
  • IMO the only advantage is that you clearly see the difference between short-circuiting && on bools and non short-circuiting & on integers. Sep 19, 2013 at 14:51
  • 1

6 Answers 6

13

The biggest one would be thwarting cryptographic timing attacks by eliminating the branches the short-circuit operators generate.

a timing attack is a side channel attack in which the attacker attempts to compromise a cryptosystem by analyzing the time taken to execute cryptographic algorithms. Every logical operation in a computer takes time to execute, and the time can differ based on the input; with precise measurements of the time for each operation, an attacker can work backwards to the input.

Information can leak from a system through measurement of the time it takes to respond to certain queries. How much such information can help an attacker depends on many variables: crypto system design, the CPU running the system, the algorithms used, assorted implementation details, timing attack countermeasures, the accuracy of the timing measurements, etc...

2
  • I had to look up both thwarting and cryptographic timing attack, other than that it's a good answer. More information available on Security SE.
    – Stijn
    Sep 19, 2013 at 13:08
  • 10
    && vs & is only about observable behavior (side-effects), which doesn't include timing. So using & doesn't help in the crypto case, because the compiler is free to optimize it to && if it knows that the right hand side has no side-effects. You need to use code that sufficiently confused the compiler that it doesn't manage to optimize your code, which in practice involves using & on integers not on bools. Sep 19, 2013 at 14:47
8

It's cute to call this eager boolean evaluation, but what the operators actually do (in C-style languages) is bitwise boolean evaluation.

The examples you've quoted work as desired, but this use does not work as desired in general. If your second example was

if ((first = foo()) & (second = bar())) { /*do something*/ }

and foo() returned 1 and bar() returned 2 -- both of which are non-zero and hence true as far as if and && are concerned -- then the if will not fire and something will not be done. This will be surprising to readers of your code.

So this is an obscure usage and a code smell. If you want to ensure that second =/Bar()'s side-effects occur, it's better to write that desire out explicitly by using separate statements, as you noted.

3
  • I knew about their use as bitwise operators, just today I saw them in an if for the first time. Not sure if eager boolean evaluation is the correct name for this.
    – Stijn
    Sep 19, 2013 at 13:48
  • 1
    I know you know :-) but the point is that if you expect them to function like eager versions of || and &&, you or your readers will be caught out. Because (1 & 2) != (1 && 2). Sep 19, 2013 at 13:52
  • 1
    The solution would be to write if (!!(first=foo()) & !!(second = bar())) but you're starting to write code that's doesn't have an immediately obvious meaning at that point. Best to just not try using bitwise operators as logical ones. Sep 19, 2013 at 16:50
3

In normal circumstances, I'd consider it highly likely to be a typo, and that the logical || or && was intended rather than the bitwise | or & operators.

There are situations where you are dealing with hardware, where the use of bitwise operators is the correct code implementation, but then, doing it inside the if statement is less than ideal for code clarity and debugging.

Likewise, if the purpose was to ensure consistent timing of code (as in thwarting cryptographic timing attacks), the use of an IF statement is likely to be a bad choice of implementation.

2

A little questionable, but I've seen code that did two nontrivial, related, boolean returning operations and combined them like your Foo | Bar example. The only real issue is that often a comment should be placed, it's too easy to glance over and ignore the single operator when all that's expected is the regular boolean comparison.

If your environment uses both in a regular setting and people are used to seeing it, I'd say its fine, but that seems unlikely. When in doubt, write the code that's easier to read. Surprise foreign syntax when you're chasing a bug just kind of sucks.

1

It is generally accepted that short-circuit (as opposed to "eager") evaluation is the correct approach, both from an optimization point of view and from the point of view of understanding typical code.

Consider:

while ((i < N) && (test(p[i]))) {
  do something
}

This kind of code is EXTREMELY common. Eager evaluation will perform the test on the array element even if the subscript is explicitly out of range. That's generally a very bad idea.

If it is critically necessary that both parts be performed, one can write something like the following:

bool part1, part2;
while ((part1 = (i < N)), (part2 = (test(p[i]))), (part1 && part2)) {
  do something
}

This uses the C comma operator to evaluate several expressions and return the value of the last one.

1

Unfortunately, using & as a replacement for && is flat out wrong. It's not just the evaluation strategies, the actual meanings of the two operators are different. There are a great many integer values where x && y has a different value than x & y. Try the following code:

#include<stdio.h>

int main()
{
        int i,j;
        int a,b;
        for (i = 0; i <= 100; i++) {
                for (j = i; j <= 100; j++) {
                        a = i && j;
                        b = i & j;
                        if (a && !b) {
                                printf("%d\t%d\n", i, j);
                        }

                }
        }

    return 0;
}

Nearly a third of the combinations have different results when comparing using & and &&. Since it's not safe to assume that a true value from a C function is anything other than non-zero, you're asking for trouble by expecting them to have the same value.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.