3

Consider the following C++ code that uses Qt's container class QMap:

#include <QMap>
#include <iostream>

QMap<int, int> myMap;

int count() {
    return myMap.size();
}

int main() {
    myMap[0] = count();
    std::cout << myMap[0] << std::endl;
    return 0;
}

Depending on whether myMap has a new entry created in it before or after count() is executed, this code's output will be either 1 or 0 respectively.

Does the output of this code depend on the implementation of QMap? Or does the C++ specification make any guarantees about when count() will be executed in relation to QMap::operator[]? Or could the result be undefined and this is a situation that's best avoided?

I ask because I had an essentially identical situation in a program I was working on. When I compiled the program in Windows and ran it using the stock Qt 5.5.1 DLLs, the result was 0. However, when I ran it using a different set of Qt 5.5.1 DLLs that had been compiled from source, the result was 1. It was an awfully confusing bug that took me a little while to track down, particularly since I got different results depending on where I ran the executable!

I'm hoping that I can understand how there was two different behaviors for the same program so that I might be able to avoid bugs like this in the future.

Bri Bri
  • 2,169
  • 3
  • 19
  • 44

3 Answers3

6

Your problem here:

myMap[0] = count();

is that the whole assignment is an expression and the call to count() is a sub-expression. There is no sequence point between expressions and sub-expressions.

This is not about evaluation order but about the ordering of side effects. An assignment has a side effect, in this case, it adds a new element to your QMap. Only at a sequence point, you have the guarantee that all side effects resulting from code before the sequence point are completed.

A function call is a sequence point, but it's between the evaluation of the function arguments and the actual calls -- not related to the return value. As you don't have any arguments here, it doesn't apply in this case.

So yes, this is undefined behavior and you should avoid it. For reference, here's a quite exhaustive answer on the topic of sequence points.


The solution of course is simple: Use two separate statements. The end of a statement (;) is always a sequence point.

  • Thanks for the detailed explanation. Of course your solution is correct and what I implemented in my code. – Bri Bri Jul 07 '17 at 23:47
0

Either side of the assignment can be evaluated first. It's analogous to a function call (and actually may well be a function call, if you have overloaded the assignment operator) such as:

  void f( X a, X b );

where either a or b may be evaluated first.

This is not necessarily specific to a particular compiler - the same compiler might choose a different order of evaluation in different circumstances.

0

Unfortunately, to my knowledge, there is no defined order, either by a given compiler, compiler version, or library for the order of evaluation, unlike a language like Python. Your code depends on undefined behavior.

There are some rules, however, that a compiler must conform to: this is not one of them. An example they give on undefined behavior is analogous to your example:

a[i] = i++; 
Alex Huszagh
  • 13,272
  • 3
  • 39
  • 67