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

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?


Solution 1:

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:

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:

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:

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.