How can “undefined” be a race condition?

Let's say I define the following C ++ object:

class AClass { public: AClass() : foo(0) {} uint32_t getFoo() { return foo; } void changeFoo() { foo = 5; } private: uint32_t foo; } aObject; 

The object is shared by two streams, T1 and T2. T1 constantly calls getFoo() in a loop to get a number (which will always be 0 if changeFoo() has not been called before). At some point, T2 calls changeFoo() to change it (without any thread synchronization).

Is there a practical likelihood that the values ​​ever obtained by T1 will differ from 0 or 5 with modern computer architectures and compilers? All the assembler code that I have studied so far is the use of 32-bit reads and writes in memory, which seems to preserve the integrity of the operation.

What about other primitive types?

Practical means that you can give an example of an existing architecture or a standard compiler where it is theoretically possible (or a similar situation with other code). I leave the modern word a bit subjective.


Edit: I see that many people notice that I should not expect 5 to be read. This is wonderful for me, and I did not say what I was doing (although thanks for pointing out this aspect). My question was more about what kind of data integrity violation can happen with the above code.

+4
source share
7 answers

In practice, all major 32-bit architectures perform 32-bit reads and writes atomically. You will never see anything other than 0 or 5.

+3
source

In practice, you will not see anything other than 0 or 5 , as far as I know (perhaps some kind of strange 16-bit architecture with 32 bits of int , where it is not).

However, whether or not you really see 5 is not guaranteed.

Suppose I am a compiler.

I see:

 while (aObject.getFoo() == 0) { printf("Sleeping"); sleep(1); } 

I know that:

  • printf cannot change aObject
  • sleep cannot change aObject
  • getFoo does not change aObject (thanks to the built-in definition)

And so I can safely convert the code:

 while (true) { printf("Sleeping"); sleep(1); } 

Since no one accesses aObject in this loop, according to the C ++ standard .

Here is what undefined behavior means: exploded expectations.

+9
source

In practice (for those who have not read the question), any potential problem boils down to whether the storage operation for unsigned int atomic operation, which on most (if not all) machines is likely to write the code for it.

Please note that this is not specified by the standard; it is specific to the architecture you are aiming at. I cannot imagine a scenario in which the calling thread will be red except 0 or 5 .

As for the name ... I do not know the varying degrees of "undefined behavior". UB - UB, this is a binary state.

+2
source

Not sure what you are looking for. On most modern architectures, there is a very clear possibility that getFoo() always returns 0 , even after changeFoo been called. Almost any decent compiler almost guaranteed that getFoo() will always return the same value regardless of any calls to changeFoo if it is called in a narrow loop.

Of course, in any real program there will be other reads and writes that will be completely out of sync regarding changes to foo .

And finally, there are 16-bit processors, and with some compilers it may also be possible that uint32_t not aligned, so the calls will not be atomic. (Of course, you only change bits in one of the bytes, so this may not be a problem.)

+2
source

Is there a practical likelihood that the values ​​ever obtained by T1 will differ from 0 or 5 with modern computer architectures and compilers? What about other primitive types?

Of course, there is no guarantee that all data will be written and read in an atomic manner. In practice, you can get the reading that happened during the partial recording. What can be interrupted, and when it happens, depends on several variables. Thus, in practice, the results can easily vary depending on the size and alignment of the types. Naturally, this difference can also be introduced, as your program moves from platform to platform and changes as ABI. In addition, the observed results may change as optimizations are added and other types / abstractions are introduced. The compiler can optimize most of your program; possibly completely, depending on the size of the instance (another variable that is not considered in the OP).

In addition to optimizers, compilers, and specific hardware pipelines: the kernel may even influence the way this memory area is processed. Does your program support Warranty , where is the memory of each object? Probably no. The memory of your object may exist on separate pages of virtual memory - what steps does your program take to ensure that memory is read and written consistently for all platforms / cores? (no, apparently)

In short: if you cannot play by the rules defined by the abstract machine, you should not use the interface of the specified abstract machine (for example, you should just understand and use the assembly if the specification of the C ++ abstract machine is really inadequate for your need - very unlikely).

All of the assembler code that I have studied so far has consisted of using 32-bit reads and writes in memory, which seems to preserve the integrity of the operation.

This is a very shallow definition of "integrity." All you have is (pseudo) consistent consistency. In addition, the compiler needs to behave as if in a scenario that is far from strict sequence. Shallow waiting means that even if the compiler did not actually optimize the breakdown and read and write in accordance with some ideal or intention, the result would be practically useless - your program would observe changes that are usually “long” after it appears.

The topic remains irrelevant, given what specifically you can Warranty .

+2
source

Undefined behavior means that the compiler can do whatever it wants. He could basically change your program to do what he likes, for example. order pizza.

See, @Matthieu M. answer for a less sarcastic version than this. I will not delete this, since I think that comments are important for discussion.

0
source

Undefined behavior is guaranteed to be undefined as the word undefined.
Technically observable behavior is pointless because it is just undefined behavior, the compiler is not needed to show you any specific behavior. It can work the way you think it should or cannot, or it can burn your computer, anything and everything is possible.

0
source

All Articles