0

I'm sure that most of you know that C does not specify which order operands to operators such as + will be evaluated.

E.g. x = exp1 + exp2 might be evaluated by first evaluating exp1, then exp2, then adding the results. However, it might evaluate exp2 first.

How would I make a C assignment that will assign either 1 or 2 to x depending on whether the left or right operand of + is evaluated first?

  • 1
    you could call 2 functions that modify a global variable. In any case this doesn't make any sense at all. The both sides may be evaluated simultaneously too – Antti Haapala -- Слава Україні Mar 22 '18 at 06:37
  • 2
    What is your actual problem? Why do you feel you need to know this? – David Heffernan Mar 22 '18 at 06:45
  • 2
    It's even worse than you think. Depending on how complicated `exp1` and `exp2` are, the evaluation of sub-expressions and side-effects can interleave. – StoryTeller - Unslander Monica Mar 22 '18 at 06:45
  • 2
    I don't understand why this question is so ill-received. It's interesting, on topic here, and valuable. It's not about a practical problem, so what? that in itself doesn't make it bad. – Andreas Grapentin Mar 22 '18 at 06:48
  • If the order is not defined, that means that the compiler may choose any order, even randomly, and not be "consistent" across two compilations or in sequence of two instructions. It may choose any suitable criterion it wants too choose order for a given expression. If you need to test for a given expression in compilation you probably need some global variable. – Jean-Baptiste Yunès Mar 22 '18 at 07:14
  • Sounds like an [XY problem](http://xyproblem.info/) to me. Please elaborate your question and tell us more about why you need to know that. – Jabberwocky Mar 22 '18 at 08:12
  • @AndreasGrapentin it's bad because of the 'write deliberately bad code for me' nature of the question. Requests to write bad code are generally ill-received and are often copypasta homework dumps. Also, I'm pretty sure that if I spent any time looking, there are dupes for C side-effects:( – Martin James Mar 22 '18 at 08:36
  • Questions for the purpose of testing the behavior of various C compilers are perfectly on-topic. Though if the code fills no practical purpose, it might be wise to tag it [tag:language-lawyer], or the question might get poorly received. – Lundin Mar 22 '18 at 09:22

3 Answers3

6

The problem with writing such an expression is that you can't write to the same variable twice inside it, or the code will invoke undefined behavior, meaning there will be a bug and anything can happen. If a variable is modified inside an expression, that same variable is not allowed to be accessed again within the same expression, unless accesses are separated by so-called sequence points. (C11 6.5/2) See this for details.

So we can't write code such as this:

// BAD CODE, undefined behavior
_Bool first=false;
x = (first?(first=true,exp1):0) + (first?(first=true,exp2):0);

This code invoked undefined behavior because the access to first between difference places of the expression are unsequenced.


However, it is possible to achieve similar code by using functions. Whenever calling a function, there is a sequence point after the evaluation of the arguments, before the function is called. And there is another sequence point before the function returns. So code such as this is possible:

n = expr(1) + expr(2)

This merely has unspecified behavior - since the evaluations of the operands to + is unspecified. But there is no unsequenced access anywhere, meaning the code can't crash etc. We simply can't know or assume what result it will give. Example:

#include <stdio.h>

static int first=0;

int expr (int n)
{
  if(first == 0)
  {
    first = n;
    return first;
  }

  return 0;
}

int main (void)
{
  int n;
  printf("%d\n", n = expr(1) + expr(2)); // unspecified order of evaluation
  first=0;
  printf("%d\n", n = expr(1) + expr(2)); // unspecified order of evaluation

  return 0;
}

This gave the output 1 1 on my system, but it might as well have given the output 1 2, 2 1 or 2 2. And the next time I execute the program, it may give a different result - we can't assume anything, except that the program will behave deterministically in an undocumented way.

The main reason why this is left unspecified, it to allow different compilers to optimize their expression parsing trees differently depending on the situation. This in turn enables fastest possible execution time and/or compile time.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • great answer. much better than mine :) – Andreas Grapentin Mar 22 '18 at 11:18
  • Just curious, where does it says that it is deterministic? The best I can find is 5.1.2.3/3 and its footnote, neither of which mentions deterministicness. – Passer By Mar 24 '18 at 18:30
  • @PasserBy Deterministic in a sense that it won't cause a crash, runaway code or other incorrect behavior. The code will behave according to one of several given possibilities, but we can't know how. In the case of operator evaluation for example, we know that either the right or the left will get evaluated first, and then the other. But we also know that the compiler will not skip any of the evaluations and their potential side-effects, something that could happen with undefined behavior. – Lundin Mar 26 '18 at 06:30
3

the simplest code to check for this would look something like this:

#include <stdio.h>

int i;

int inc_i(void)
{
  return ++i;
}

int get_i(void)
{ 
  return i;
}

int
main (void)
{
  i = 0;
  printf("%i\n", get_i() + inc_i());
  i = 0;
  printf("%i\n", inc_i() + get_i());
}

on my system, this prints:

$ ./test 
 1
 2

which tells me that the left side of the expression is evaluated first. However, while I think that this is an interesting thought experiment, I would not recommend relying on this check to do any meaningful logic in your program - that would be asking for trouble.

Andreas Grapentin
  • 5,499
  • 4
  • 39
  • 57
  • "on my system". No. That particular expression, surrounded by those other particular expressions, in this particular version of your compiler, at this particular time of day, with these particular compilation flags, happened to produce those results the few times your tried it. The result is undefined. – Art Mar 22 '18 at 06:59
  • 1
    @Art No it isn't UB. Don't go around down-voting answers because _you_ lack knowledge. This code invokes _unspecified_ behavior, not undefined. All accesses to `i` are sequenced. There is a sequence point before a function is called and another before it is returned. – Lundin Mar 22 '18 at 07:54
  • @Lundin Fine. It's not undefined. The function calls are indeterminately sequenced. For all practical purposes the result is the same because " In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations". Which is why answers with "on my system" are always dangerous. – Art Mar 22 '18 at 08:13
  • @Art The whole purpose of this program in the first place is to play around with the unspecified order of evaluation. Hence "on my system". – Lundin Mar 22 '18 at 08:57
  • @Art No, the result is not the same. With undefined behaviour, the compiler is allowed to (and sometimes does) make dæmons fly out of your nose. With unspecified behaviour, the compiler must make one out of multiple choices each time the expression is encountered and the set of possible behaviours is limited. – fuz Mar 22 '18 at 11:13
-1

You can't. Not reliably. Any time you allow a situation like that to happen in your code the C standard calls it "undefined behavior" and allows the compiler to do whatever it wants. And compilers are notorious, especially in the recent decades, for being completely unpredictable when they encounter undefined behavior. The worst part is that it might even be hard to configure the compiler to warn you that such a situation happened (it can't always know). It is your responsibility to never allow a situation like this to happen.

This might sound crazy but it has a good point. CPUs are different. On some it might make sense to do things in one order. On another that same order will be extremely slow, but doing things in another order is very fast. Maybe the next generation of a CPU will change how instructions are executed and now that third order of operations makes the most sense. Maybe the compiler developers figured out a way to generate much better code by doing things a certain way.

Just because you manage to convince the compiler to generate code that generates a particular order of operations one time doesn't mean that it won't change next time you compile. Maybe the order of the operands doesn't matter at all and they are evaluated in whatever order they happen to be stored in some internal data structure in the compiler and that happens to be a hash table with a hash function that changes on every execution, or maybe the hash function changes next time you update the compiler, or you happen to just add another expression before or after in your code which changes the order, or update another program on your computer that also updates a library that the compiler uses and that library happens to change the order of how some things are stored in a data structure which will change the order of how the compiler generates code.

Note. It is possible to write the expression you want where the order of evaluation wouldn't cause undefined behavior, only be "indeterminately sequenced". In that case the compiler isn't free to do whatever it wants, it is just allowed to evaluate the expressions in whatever order it wants. In most practical situations the result is the same.

Art
  • 19,807
  • 1
  • 34
  • 60
  • Thank you. more detailed than my comment! Perfect. – Jean-Baptiste Yunès Mar 22 '18 at 07:14
  • The order of evaluation is left unspecified on purpose by the committee, for the purpose of allowing compilers to implement their expression parsing trees differently. This was likely because ISO standards are not allowed to favour a certain existing technology on the market. So I very much doubt this has anything to do with CPUs, that's just speculation. – Lundin Mar 22 '18 at 09:20
  • 1
    It is not true that any incompletely sequenced evaluation has undefined behavior. The C standard distinguishes *unsequenced* and *indeterminately sequenced* evaluations. The latter does not cause undefined behavior. While the OP’s question is problematic, it is possible to insert function calls to reveal some aspects of evaluation order. This order can of course not be relied on, as it is indeterminate, but we should not make false statements about what the standard says, as those can lead to further misunderstanding. – Eric Postpischil Mar 22 '18 at 09:25
  • Downvote because evaluation order is *unspecified,* not *undefined,* making most of your argumentation not apply. – fuz Mar 22 '18 at 11:11