Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C multiple assignments to same variable in short-circuited expression

Tags:

c

I have three variables: a, b, c. Let's say they are integers. I want to find the first non-zero value among them, in that particular order, without looping. The following seems to work, but I am not sure if that is because I am lucky, or because the language guarantees it:

int main(int argc, char *argv[]) {
    int a = 0;
    int b = 3;
    int c = 5;
    int test;

    if ((test = a) != 0 || (test = b) != 0 || (test = c) != 0) {
        printf("First non-zero: %d\n", test);
    } else {
        printf("All zero!\n");
    }

    return 0;
}

Is the repeated assignment with short-circuiting shown here guaranteed to work as intended, or am I missing something?

This might be one place where a three-letter answer would be acceptable, but a two-letter answer might require more explanation.

like image 891
Mad Physicist Avatar asked May 12 '20 06:05

Mad Physicist


3 Answers

It would!

Because of the nature of the OR operator if any of the condition is true then the test stops.

Thus i think what you did was basically equivalent to:

test = a != 0 ? a : b != 0 ? b : c != 0 ? c : 0;
printf("%d\n",test);

but heck yours looks good.

[update]

As per what chqrlie mentioned it can be further simplified to:

test = a ? a : b ? b : c;
like image 124
Jackson Avatar answered Nov 14 '22 23:11

Jackson


Yes, your expression is fully defined because there is a sequence point at each || operator and the short circuit evaluation guarantees that the first non zero value assigned to test completes the expression.

Here is a crazy alternative without sequence points that may produce branchless code:

int test = a + !!a * (b + !!b * c);
printf("%d\n", test);
like image 27
chqrlie Avatar answered Nov 14 '22 21:11

chqrlie


The code is very bad practice but it is guaranteed to work fine.

This is because the || and && operators have special characteristics - unlike most operators in C, they guarantee that the evaluation of the left operand is sequenced (executed) before the evaluation of the right operand. This is the reason that the code works. There's also a guarantee that the right operand will not be evaluated if it is sufficient to evaluate the left one ("short circuit"). Summarized in C17 6.5.14/4:

Unlike the bitwise | operator, the || operator guarantees left-to-right evaluation; if the second operand is evaluated, there is a sequence point between the evaluations of the first and second operands. If the first operand compares unequal to 0, the second operand is not evaluated.

"Sequence point" being the key here, which is what gives the expression a deterministic outcome.

Had you used pretty much any other operator (like for example bitwise |), then the result would be undefined, because you have multiple side effects (assignments) on the same variable test in the same expression.

A more sound version of the same algorithm would involve storing the data in an array and loop through it.

like image 43
Lundin Avatar answered Nov 14 '22 21:11

Lundin