Why is this version of logical AND in C not showing short-circuit behavior?
Asked Answered
P

6

80

Yes, this is a homework question, but I've done my research and a fair amount of deep thought on the topic and can't figure this out. The question states that this piece of code does NOT exhibit short-circuit behavior and asks why. But it looks to me like it does exhibit short-circuit behavior, so can someone explain why it doesn't?

In C:

int sc_and(int a, int b) {
    return a ? b : 0;
}

It looks to me that in the case that a is false, the program will not try to evaluate b at all, but I must be wrong. Why does the program even touch b in this case, when it doesn't have to?

Pensionary answered 13/10, 2014 at 15:17 Comment(12)
As is typical of most contrived homework questions, you'll never see this kind of code in a production system, unless the programmer is deliberately trying to be clever.Separatist
@RobertHarvey You'll see code exactly like this in production systems all the time! It's not likely to be a function named AND(), but functions receiving arguments by value and then evaluating them (or not) depending on the logic of the function are everywhere. Despite being a "trick question", it's a critical behavior of C to understand. In a language with call-by-name this question would have a very different answer.Thirsty
@BenJackson: I was commenting on the code, not the behavior. Yes, you need to understand the behavior. No, you don't need to write code like this.Separatist
This is actually quite relevant if you ever have to code in VB and encounter IIf. Because it is a function rather than an operator, evaluation is not short-circuited. This can create problems for developers used to the short-circuiting operator who then write things like IIf(x Is Nothing, Default, x.SomeValue).Crooks
@Thorpe because it's a indictment of the education system.Chancellorsville
VB has this exact function, called IIf. We had quite a few "engineers" who couldn't figure out that both sides of IIf were evaluated.Meany
If you were to use function pointers and overloads for each option; (bool,bool / bool,fun / fun,bool / fun,fun) would it then be possible? granted you'd have to put the function pointers etc in the expression not the function itself, but (although contrived) I could see it working.Phantasmagoria
This question makes me glad I'm done with school. :-)Anapest
Strictly, neither int, 'a' nor 'b' will be 'false', since no such value exists in C. You have two ints. Do they both need to be copied? I suppose you would have to look to the standard to see whether the evaluation of 'b' could be short-circuited. It does seem that a run-time optimization could skip the evaluation of 'b'. It would involve re-ordering code in the caller to do so, if it could determine that 'b' was not needed on the stack.Meliorate
@FredMitchell: false and true in C are sets, not values. In particular, true is the set of all non-zero values. You need to have true and false to be able to do boolean logic, and nobody denies you can do boolean logic in C. As for skipping b, evaluation is mandatory for observable side effects (only).Embellishment
The ternary operator does short-circuit. You can test this by replacing a and b with functions that include side effects. For example: ideone.com/WHIS2vGrapefruit
It's worth pointing out that, in addition to the lack of short-circuit behavior, this sc_and function isn't equivalent to the && operator. sc_and(2, 3) yields 3; 2&&3 yields 1. (The result does have the same truth value, though.)Forenamed
D
118

This is a trick question. b is an input argument to the sc_and method, and so will always be evaluated. In other-words sc_and(a(), b()) will call a() and call b() (order not guaranteed), then call sc_and with the results of a(), b() which passes to a?b:0. It has nothing to do with the ternary operator itself, which would absolutely short-circuit.

UPDATE

With regards to why I called this a 'trick question': It's because of the lack of well-defined context for where to consider 'short circuiting' (at least as reproduced by the OP). Many persons, when given just a function definition, assume that the context of the question is asking about the body of the function; they often do not consider the function as an expression in and of itself. This is the 'trick' of the question; To remind you that in programming in general, but especially in languages like C-likes that often have many exceptions to rules, you can't do that. Example, if the question was asked as such:

Consider the following code. Will sc_and exibit short-circuit behavior when called from main:

int sc_and(int a, int b){
    return a?b:0;
}

int a(){
    cout<<"called a!"<<endl;
    return 0;
}

int b(){
    cout<<"called b!"<<endl;
    return 1;
}

int main(char* argc, char** argv){
    int x = sc_and(a(), b());
    return 0;
}

It would be immediately clear that you're supposed to be thinking of sc_and as an operator in and of itself in your own domain-specific language, and evaluating if the call to sc_and exhibits short-circuit behavior like a regular && would. I would not consider that to be a trick question at all, because it's clear you're not supposed to focus on the ternary operator, and are instead supposed to focus on C/C++'s function-call mechanics (and, I would guess, lead nicely into a follow-up question to write an sc_and that does short-circuit, which would involve using a #define rather than a function).

Whether or not you call what the ternary operator itself does short-circuiting (or something else, like 'conditional evaluation') depends on your definition of short-circuiting, and you can read the various comments for thoughts on that. By mine it does, but it's not terribly relevant to the actual question or why I called it a 'trick'.

Diaphanous answered 13/10, 2014 at 15:23 Comment(16)
Note that this is one argument in favor of using macros for some things in C. :-)Halftimbered
The ternary operator does short-circuting? No. It evaluates one of the expressions following the ?, just like in if (condition) {when true} else {when-false}. That's not called short circuiting.Alage
@Jens; The ternary operator does short-circuting? YES. In fact it does. The short-circuit expression x Sand y (using Sand to denote the short-circuit variety) is equivalent to the conditional expression if x then y else false; the expression x Sor y is equivalent to if x then true else y..Ferrite
@R.. this is an argument for using a purely functional language too.Laski
@haccks: Also from your link: "Short-circuit evaluation... denotes the semantics of some Boolean operators in some programming languages" (emphasis mine). One would not say that if(a()) return b(); return false; "short circuts" despite the fact it it is semantically identical to return a() && b();Realm
the interesting question is: what happens when the compiler inlines the function?Shorter
@ths: inlining isn't permitted to affect observable behavior. Therefore if evaluating the second argument of the call has side-effects, the function after inlining must still evaluate it regardless of the value of a.Crowd
@Jens: I'd call any case where an operator skips evaluation of one or more of its operands a form of "short circuiting", but this is really just a matter of how you choose to define the term.Halftimbered
@R.. that's only the case if it's made very very abundantly clear that the programmer should expect short-circuit evaluation, more often than not this is an argument against using macros! If I make a call to a function afun(side_effects(), side_effects2()) I rarely want afun to turn around and NOT evaluate those arguments! (of course this is very true in C and not so true in truly functional languages)Saraann
@Luis: Indeed, I agree.Halftimbered
@R.. I'd call only && and || short-circuiting. If you call ?: short circuitnig you'd have to call if() short circuiting, since ?: is only syntactic sugar for an if.Alage
@Alage It's syntactic sugar for an if else, not an if. And even then, it's not really; the ?: operator is an expression, if-else is a statement. Those are semantically very different things, even if you can construct an equivalent set of statements that produce the same effect as the ternary expression. This is why it is considered short-circuiting; most other expressions always evaluate all of their operands (+,-,*,/ etc). When they don't, it's a short circuit (&&, ||).Diaphanous
@Alage I would have agreed with you at the start, but it seems that "short-circuiting" refers to operators, specifically ones that may not evaluate all of their operands. The ?: operator fits this definition. if-else is not an operator.Wheaton
Yes, to me the important distinction is that we're talking about operators. Of course flow control statements control which code path gets executed. For operators, it's non-obvious to someone not familiar with C and C-derived languages that an operator might not evaluate all of its operands, so it's important to have a term for talking about this property, and for that, I use "short circuiting".Halftimbered
@aruisdante: The conditional operator is absolutely not just "syntactic sugar for an if else".Subcommittee
I can't have this answer to avoid the terms eager evaluation.Lapointe
F
43

When the statement

bool x = a && b++;  // a and b are of int type

executes, b++ will not be evaluated if the operand a evaluated to false (short circuit behavior). This means that the side-effect on b will not take place.

Now, look at the function:

bool and_fun(int a, int b)
{
     return a && b; 
}

and call this

bool x = and_fun(a, b++);

In this case, whether a is true or false, b++ will always be evaluated1 during function call and side effect on b will always take place.

Same is true for

int x = a ? b : 0; // Short circuit behavior 

and

int sc_and (int a, int b) // No short circuit behavior.
{
   return a ? b : 0;
} 

1 Order of evaluation of function arguments are unspecified.

Ferrite answered 13/10, 2014 at 15:49 Comment(5)
OK, so coroborating with Stephen Quan's answer: is it possible (legal) that a compilar could inline the "and_fun" function, such that when you call "bool x = and_fun(a, b++);" b++ will not be incremented if a is true ?Chancellorsville
@ShivanDragon That sounds like changing observable behaviour to me.Baccate
@ShivanDragon: When inlining the compiler will not change the behaviour. It is not as simply as substituting the function body in.Hypertensive
For clarity, you might add int x = a ? b++ : 0, as observable short-circuiting.Antisana
@PaulDraper; I kept the original snippet as it is for readers not to confuse.Ferrite
T
19

As already pointed out by others, no matter what gets pass into the function as the two arguments, it gets evaluated as it gets passed in. That is way before the tenary operation.

On the other hand, this

#define sc_and(a, b) \
  ((a) ?(b) :0)

would "short-circuit", as this macro does not imply a function call and with this no evaluation of a function's argument(s) is performed.

Thorpe answered 13/10, 2014 at 16:54 Comment(1)
maybe explain why? It looks for me exactly the same as the snippet of the op. Except its inline isntead of a different Scope.Cyril
P
5

Edited to correct the errors noted in @cmasters comment.


In

int sc_and(int a, int b) {
    return a ? b : 0;
}

... the returned expression does exhibit short-circuit evaluation, but the function call does not.

Try calling

sc_and (0, 1 / 0);

The function call evaluates 1 / 0, though it is never used, hence causing - probably - a divide by zero error.

Relevant excerpts from the (draft) ANSI C Standard are:

2.1.2.3 Program execution

...

In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

and

3.3.2.2 Function calls

....

Semantics

...

In preparing for the call to a function, the arguments are evaluated, and each parameter is assigned the value of the corresponding argument.

My guess is that each argument is evaluated as an expression, but that the argument list as a whole is not an expression, hence the non-SCE behaviour is mandatory.

As a splasher on the surface of the deep waters of the C standard, I'd appreciate a properly informed view on two aspects:

  • Does evaluating 1 / 0 produce undefined behaviour?
  • Is an argument list an expression? (I think not)

P.S.

Even you move to C++, and define sc_and as an inline function, you will not get SCE. If you define it as a C macro, as @alk does, you certainly will.

Passbook answered 15/10, 2014 at 8:1 Comment(2)
No, an inline function does not change the semantics of a function call. And those semantics specify that all arguments are evaluated, as you correctly cited. Even if the compiler can optimize the call, visible behavior must not change, i. e. sc_and(f(), g()) must behave as if both f() and g() are always called. sc_and(0, 1/0) is a bad example because it is undefined behavior, and the compiler is not required to even call sc_and()...Medlock
@cmaster Thank you. I simply did not know that C++ inline preserved call semantics. I've stuck with the zero-divide, as it fits the example, and SCE I think is often exploited to avoid undefined behaviour.Passbook
C
4

To clearly see ternary op short circuiting try changing the code slightly to use function pointers instead of integers:

int a() {
    printf("I'm a() returning 0\n");
    return 0;
}

int b() {
    printf("And I'm b() returning 1 (not that it matters)\n");
    return 1;
}

int sc_and(int (*a)(), int (*b)()) {
    a() ? b() : 0;
}

int main() {
    sc_and(a, b);
    return 0;
}

And then compile it (even with almost NO optimization: -O0!). You will see b() is not executed if a() returns false.

% gcc -O0 tershort.c            
% ./a.out 
I'm a() returning 0
% 

Here the generated assembly looks like:

    call    *%rdx      <-- call a()
    testl   %eax, %eax <-- test result
    je      .L8        <-- skip if 0 (false)
    movq    -16(%rbp), %rdx
    movl    $0, %eax
    call    *%rdx      <- calls b() only if not skipped
.L8:

So as others correctly pointed out the question trick is to make you focus on the ternary operator behaviour that DOES short circuit (call that 'conditional evaluation') instead of the parameter evaluation on call (call by value) that DOES NOT short circuit.

Chopstick answered 15/10, 2014 at 7:39 Comment(0)
C
0

The C ternary operator can never short-circuit, because it only evaluates a single expression a (the condition), to determine a value given by expressions b and c, if any value might be returned.

The following code:

int ret = a ? b : c; // Here, b and c are expressions that return a value.

It's almost equivalent to the following code:

int ret;
if(a) {ret = b} else {ret = c}

The expression a may be formed by other operators like && or || that can short circuit because they may evaluate two expressions before returning a value, but that would not be considered as the ternary operator doing short-circuit but the operators used in the condition as it does in a regular if statement.

Update:

There is some debate about the ternary operator being a short-circuit operator. The argument says any operator that doesn't evaluate all it's operands does short-circuit according to @aruisdante in the comment below. If given this definition, then the ternary operator would be short-circuiting and in the case this is the original definition I agree. The problem is that the term "short-circuit" was originally used for a specific kind of operator that allowed this behavior and those are the logic/boolean operators, and the reason why are only those is what I'll try to explain.

Following the article Short-circuit Evaluation, the short-circuit evaluation is only referred to boolean operators implemented into the language in a way where knowing that the first operand will make the second irrelevant, this is, for the && operator being the first operand false, and for the || operator being the first operand true, the C11 spec also notes it in 6.5.13 Logical AND operator and 6.5.14 Logical OR operator.

This means that for the short-circuit behavior to be identified, you would expect to identify it in an operator that must evaluate all operands just like the boolean operators if the first operand doesn't make irrelevant the second. This is in line with what is written in another definition for the short-circuit in MathWorks under the "Logical short-circuiting" section, since short-circuiting comes from the logical operators.

As I've been trying to explain the C ternary operator, also called ternary if, only evaluates two of the operands, it evaluates the first one, and then evaluates a second one, either one of the two remaining depending on the value of the first one. It always does this, its not supposed to be evaluating all three in any situation, so there is no "short-circuit" in any case.

As always, if you see something is not right, please write a comment with an argument against this and not just a downvote, that just makes the SO experience worse, and I believe we can be a much better community that one that just downvotes answers one does not agree with.

Chondro answered 14/10, 2014 at 10:51 Comment(15)
And why the downvote? I'd like comments so I can fix whatever I've said wrong. Please be constructive.Nadanadab
Maybe -1 because it is not 100% On Topic. but anyway I gave +1 because your answer at least explains it statemant and it doesn't seem to be wrong ompv.Cyril
Thanks Zaibis for give me the idea it may not be clear how this fits for an answer. The question is about why that piece of code doesn't short circuit. The thing is that I assume the function declaration doesn't need to be explained, I've explained the body of the function and the only body in the function is a ternary operator in a very simple form. That's why I've focused on why the ternary operator doesn't short-circuit.Nadanadab
@AdrianPerez see the comments section in my answer for why most people would 100% consider the terniary operator to shor-circuit. It's an expression that doesn't evaluate all its operands based on the value of some of its operands. That's short-circuiting.Diaphanous
@Diaphanous I've updated my answer, I think the problem is in that definition. Do you have any references for that definition?Nadanadab
The wiki on short circuit evaluation is pretty good.Diaphanous
@Diaphanous can you point where in that document that I've also used mentions that applies to every operator that doesn't evaluate all the operands?. Following that same doc, right at the start says it applies to boolean operators, because of the nature of and and or operators.Nadanadab
I can't think of any non-boolean operator that doesn't evaluate all of its operands . But that's besides the point; the reason it's called short-circuiting is precisley because it's an operator (or in the general sense, an expression rather than a statement) that doesn't evaluate all of its operands. The type of the operator doesn't really matter. It would be perfectly possible to write boolean operators that didn't short circuit, and you could write non-boolean ones that did as well (ex: integer muliplicaton of 0 always is 0, so return 0 immediately if first operand is 0).Diaphanous
The point is not exactly because it has to be a boolean operator, I agree on your attempt to generalize and abstract this idea, but the point is that for it to be a short-circuit operator it has to have a first operand for which the next one becomes irrelevant to compute the value where if the value that makes it is not present as first operand, both (or all if we generalize as you try to do) operands have to be evaluated. This only seem to happen in boolean operators and doesn't happen in the ternary-if operator as it always in all cases evaluates two of three operands.Nadanadab
Correct, but that's different that the equivalent operator in other languages. As pointed out in the comments, VB has its own terniary that does in fact evaluate all operands because it acts as a function call . Again, you're treating the ternary operator like it's 'special'. It's not, it's an operator like any other operator. And you need to have a way of stating that it doesn't always evaluate all of its operands, so saying it 'short circuits' is the generally accepted way to do so. It doesn't matter that it always short circuits, just that it does.Diaphanous
I think you are offtopic now, I'm talking about C ternary operator, I haven't said a word about VB ternary operator and it doesn't have anything to do with the OP question. Also, the argument that you are using that "It doesn't matter that it always short circuits, just that it does" is misleading, since you seem to try to ignore the fact that this short-circuiting idea comes from the boolean operators that I've just talked to you, and I've provided to you references that corroborates this fact, and I can provide more.Nadanadab
Short-circuits only happen in one (or some if we think abstract) cases for the value of the first operand. If we ignore this fact, we aren't abstracting this properly. Also, the OP didn't want us to abstract the idea. He only wanted to know why that code doesn't short-circuit, and I've provided such an answer for which counter-arguments are pointed to a possible abstraction of this short-circuiting behavior in operators.Nadanadab
The whole reason it's called a ternary operator is precisely because it has three operands. ternary literally means "composed of three items". Nothing in any definition of short circuiting ever stated that it only considers two operands, or that the first element is the only one that matters for the short-circuit. You could in theory define a language such that, for instance, had a syntax where boolean operators had N operands, and would still short-circuit in a left-to-right manner (to be consistent with the behavior of a series of chained two-operand versions).Diaphanous
Again, most people's accepted definition of 'short circuiting' is an operator where not all of the operands are evaluated depending on the value of some of the operands. If you accept that as your definition, then the ternary operator qualifies. If you don't, then fine, but argue to that point specifically.Diaphanous
x = a ? b : 0; is semantically the same as (a && (x = b)) || (x = 0);Fovea

© 2022 - 2024 — McMap. All rights reserved.