Strange behavior of std :: map

Next test program

#include <map> #include <iostream> using namespace std; int main(int argc, char **argv) { map<int,int> a; a[1]=a.size(); for(map<int,int>::const_iterator it=a.begin(); it!=a.end(); ++it) cout << "first " << (*it).first << " second " << (*it).second << endl; } 

leads to a different exit when compiling on g++ 4.8.1 (Ubuntu 12.04 LTS):

 g++ xxx.cpp ./a.out first 1 second 1 

and on Visual Studio 2012 (Windows 7) (standard Win32 console application project):

 ConsoleApplication1.exe first 1 second 0 

Which compiler is right? Am I doing something wrong?

+50
c ++ g ++ visual-studio-2012
Jan 15 '14 at 9:37
source share
3 answers

This is a really well-formed program that has two equally valid execution paths, so both compilers are right.

 a[1] = a.size() 

In this expression, evaluating two operands = has no effect.

§1.9 / 15 [intro.execution] Unless noted, evaluations of the operands of individual operators and subexpressions of individual expressions have no consequences.

However, function calls do not alternate, so the operator[] and size calls are actually indefinitely sequenced rather than sequence-dependent.

§1.9 / 15 [intro.execution] Each estimate in the calling function (including other function calls) that is not otherwise specifically sequenced before or after the body of the called function is executed is indefinitely ordered with respect to the execution of the called function.

This means that function calls can be made in one of two orders:

  • operator[] then size
  • size , then operator[]

If the key does not exist and you call operator[] with this key, it will be added to the map, thereby resizing the map. Thus, in the first case, the key will be added, the size will be received (now it is 1), and 1 will be assigned to this key. In the second case, the size will be obtained (which is 0), the key will be added, and 0 will be assigned to this key.

Note that this is not a situation that causes undefined behavior. undefined behavior occurs when two modifications or modifications and the reading of the same scalar object are not affected.

§1.9 / 15 [intro.execution] If the side effect of a scalar object does not affect any other side effect on the same scalar object or the calculation of a value using the value of the same scalar object, the behavior is not defined.

In this situation, they are not biased, but vaguely ordered.

So, we have two equally valid order of execution of the program. Or it can happen, and both give a reliable result. This is unspecified behavior.

§1.3.25 [defns.unspecified]
unspecified behavior
behavior, for a well-formed program design and correct data, which depends on the implementation




So, to answer your questions:

Which compiler is right?

Both of them.

Am I doing something wrong?

Maybe. It is unlikely that you will want to write code that has two execution paths like this. Undefined behavior may be okay, unlike undefined behavior, because it can be allowed for one observable output, but it’s not worth it in the first place if you can avoid it. Instead, do not write code that has such ambiguity. Depending on what exactly you want to set the correct path, you can do one of the following:

 auto size = a.size(); a[1] = size; // value is 0 

Or:

 a[1]; a[1] = a.size(); // value is 1 

If you want the result to be 1 , and you know that the key does not exist yet, you can of course do the first code, but assign size + 1 .

+77
Jan 15 '14 at 10:10
source share

In this case, where a[1] returns a primitive type, see this answer . In the case where the value type std::map is a user-defined type and operator=(T, std::size_t) defined for this type, the expression:

 a[1] = a.size(); 

can be converted to a version with less syntactic sugar:

 a[1] = a.size(); a.operator[](1) = a.size(); operator=(a.operator[](1), a.size()); 

And, as we all know from §8.3.6 / 9:

The evaluation order of the function arguments is not specified.

which leads to the fact that the result of the above expression is unspecified.

We have, of course, two cases:

  • If a.operator[](1) is evaluated first, the size of the map is increased by 1, which leads to the first result ( first 1 second 1 ).
  • If a.size() is evaluated first, the output you get is second ( first 1 second 0 ).
+15
Jan 15 '14 at 10:00
source share

This is called the sequence-point problem, which means that certain operations can be performed in any order selected by the compiler.

If someone has side effects on another, it’s called “undefined behavior”, a bit like “undefined behavior”, however, when the result should be one of a fixed subset of the results, so here it should be either 0 or 1 and not may be of a different meaning. In fact, you should usually avoid this.

In your particular case. execution of operator [] on the map changes its size (if this element does not exist yet). Thus, it has a side effect on the right side of what it assigns to it.

+14
Jan 15 '14 at 9:40
source share



All Articles