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

CShort Circuiting

C Problem Overview


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?

C Solutions


Solution 1 - C

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'.

Solution 2 - C

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.

Solution 3 - C

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.

Solution 4 - C

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.

Solution 5 - C

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.

Solution 6 - C

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBobazonskiView Question on Stackoverflow
Solution 1 - CaruisdanteView Answer on Stackoverflow
Solution 2 - ChaccksView Answer on Stackoverflow
Solution 3 - CalkView Answer on Stackoverflow
Solution 4 - CThumbnailView Answer on Stackoverflow
Solution 5 - CflaviodesousaView Answer on Stackoverflow
Solution 6 - CAdrián PérezView Answer on Stackoverflow