Assume an API in which every function returns an error code which is zero in case of no error and nonzero for error values.
Let
int foo(...);
int bar(...);
be functions in this API. Let there be a code fragment in which foo
and bar
have to be called in order and foo and bar should always be called, regardless of previous error but the first returned nonzero error code is to be propagated, i.e.
int foobar(...)
{
int rc = 0, rc_;
/* ... */
rc_ = foo(...); rc = rc ? rc : rc_;
rc_ = bar(...); rc = rc ? rc : rc_;
return rc;
}
Writing the rc,rc_ multiplexing is tiring and error prone (no matter if a ternary operator, if/else or something else is used).
Let there be an error propagating helper function
static inline
int rc_propagate(int r, int p){ return p ? p : r; }
This could be used in foobar
like this
int foobar(...)
{
int rc = 0;
/* ... */
rc = rc_propagate(foo(...), rc);
rc = rc_propagate(bar(...), rc);
return rc;
}
Does the C standard allow to optimize by pulling the evaluation of the first parameter of rc_propagate
, a static inline function, into the ternary so that it may not get executed due to the ternary operator evaluation rules if the second parameter p
were nonzero?
A compiler(or hardware for that matter) is allowed to optimize the program1 as long as the program stays the same, i.e. you cannot prove that the program that was executed was different from the one you wrote.
In this case the program you wrote will always call the external function because it isn't present in the ternary operator where it may not be evaluated. The function may have different interesting side-effects and is unknown to the compiler. This means that the optimized version of the program will have to call the external function at some point (code may be reordered) to preserve that behavior.
If that weren't true you could prove that the program that was executed wasn't the same as the one you wrote. Doing this would be easy; put a printf(or equivalent) statement into the external function call.
1. A concept of an abstract program which exists when it is executing, not the generated machine code or executable.
Using an argument from authority, you can see that no compiler will actually optimize the calls to foo() and bar() out:
gcc versions 4.9.2, 5.3,or 6.1 with -O2:
foobar():
pushq %rbx
call foo()
movl %eax, %ebx
call bar()
testl %ebx, %ebx
cmove %eax, %ebx
movl %ebx, %eax
popq %rbx
ret
clang versions 3.7.1, or 3.8 with -O2:
foobar():
pushq %rbx
callq foo()
movl %eax, %ebx
callq bar()
testl %ebx, %ebx
cmovnel %ebx, %eax
popq %rbx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With