Post Reply 
C Evaluation
07-14-2022, 08:06 PM (This post was last modified: 07-14-2022 08:13 PM by toml_12953.)
Post: #1
C Evaluation
What should be printed by the code below? One compiler I used printed

reached f1
reached f2
2

Another printed

reached f2
reached f1
2

Which one is right? Here's the code:

Code:
#include <stdio.h>

short f1(){
    printf("reached f1\n");
    return (1);
}
short f2(){
    printf("reached f2\n");
    return (2);
}

void main(){
    short x;
    x = f1() * f2();
    printf("%d\n",x);
}

Tom L
Cui bono?
Find all posts by this user
Quote this message in a reply
07-14-2022, 08:34 PM
Post: #2
RE: C Evaluation
Both are right. C does not specify the order of which the operands of an operator are evaluated. There are exceptions but '*' is not one of them.

Try CC41!
Find all posts by this user
Quote this message in a reply
07-14-2022, 11:44 PM (This post was last modified: 07-14-2022 11:45 PM by cruff.)
Post: #3
RE: C Evaluation
To further clarify what Craig B. is saying, the C language specification talks about "sequence points", and permits the compiler to reorder expressions as long as the meaning is not changed. However there is much room for unspecified behavior. If you need to force a specific evaluation order, you must use statements separated by semicolons with intermediate values. You also need to be wary of statements like the following:
Code:
i = 0;
i = ++i + ++i;

Where you could possibly get answers of 2 or 3. A good compiler will warn you about this, for example from LLVM clang:

Code:
% cc -o t t.c
t.c:8:9: warning: multiple unsequenced modifications to 'i' [-Wunsequenced]
    i = ++i + ++i;
        ^     ~~
1 warning generated.
% ./t
3

Things get even more complex if you are parallel programming and you need to enforce the order of memory accesses as seen by CPUs and DMA devices in a system, but that is a larger subject you probably don't care about at the moment.
Find all posts by this user
Quote this message in a reply
07-15-2022, 01:12 AM
Post: #4
RE: C Evaluation
Try Compiler Explorer, it support several languages, compilers and targets.
When you are not sure on how the C code get translated into assembly, Compiler Explorer will show you.

I am curious, what compilers did you used ?

I have tried several C compilers and they all gave me this output:
Code:
reached f1
reached f2
2

I have modified you main function to follow the standard
Code:
int main(int argc, char* argv[]) {
    short x;
    x = f1() * f2();
    printf("%d\n",x);
    return 0;
}
Find all posts by this user
Quote this message in a reply
07-15-2022, 11:20 PM
Post: #5
RE: C Evaluation
If you have ever worked with lex and yacc (Yet Another Compiler Compiler) you would understand why evaluation order is not guaranteed. The "natural" way to use yacc puts the operands and operators on a stack and when you "pop" them, they come out in reverse order.

C does have the comma operator which guarantees left to right evaluation, but the commas in function calls are *not* comma operators.

If expression does guarantee left to right evaluation and short-circuiting so that you can do something like:
if (p && (p->status == 0))

so that p->status is not evaluated if p is NULL.
Find all posts by this user
Quote this message in a reply
07-16-2022, 05:02 PM (This post was last modified: 07-16-2022 05:02 PM by ijabbott.)
Post: #6
RE: C Evaluation
(07-15-2022 11:20 PM)KeithB Wrote:  If expression does guarantee left to right evaluation and short-circuiting so that you can do something like:
if (p && (p->status == 0))

so that p->status is not evaluated if p is NULL.

It's the && and || operators that guarantee left to right evaluation and short-circuiting. The `if` only cares whether the expression compares equal to 0 or not.

— Ian Abbott
Find all posts by this user
Quote this message in a reply
07-16-2022, 06:43 PM
Post: #7
RE: C Evaluation
(07-15-2022 11:20 PM)KeithB Wrote:  If you have ever worked with lex and yacc (Yet Another Compiler Compiler) you would understand why evaluation order is not guaranteed. The "natural" way to use yacc puts the operands and operators on a stack and when you "pop" them, they come out in reverse order.

To nit a bit:

Runtime evaluation order in C does not depend on a compiler's "front end" (parsing and semantic analysts). LALR (lookahead LR) parsers Yacc and Bison parse C source code left to right (from source code begin to end, hence LR parsing) and build the parse tree bottom-up towards the root, thereby performing a "right most derivation" (hence LR) to produce intermediate (IR) code. The compiler's "back end" then optimizes and translates the IR to the target representation (assembly, machine code, byte code, stack machine, ...). Because C allows aggressive optimization of the IR and the target representation, operands to operators and arguments passed to functions may be evaluated in an unspecified order to facilitate those optimizations, unlike in Java for example. Consider f(++k)+a[k] which exhibits undefined behavior in C. This is on purpose, because we may want to pre-fetch a[k] before the function call f(++k) to hide the latency of the memory fetch that can take many cycles to complete, even with fast L1/L2 caches. Once f(++k) is done, a[k] is ready to be consumed by the CPU so to speak. Of course, it is still possible to call f(k+1) instead of f(++k) to preserve k in a[k] and then we can increment ++k, but that's not the point. Early compilers were just much simpler and didn't have to bother with that. Without going into too much detail, there is another reason why C (and other PL) typically evaluate function arguments in reverse order (but not always as we know!), which has to do with the way subroutine frames are populated in the generated target code.

- Rob

"I count on old friends to remain rational"
Visit this user's website Find all posts by this user
Quote this message in a reply
Post Reply 




User(s) browsing this thread: 3 Guest(s)