34

I am looking at a C++ class which has the following lines:

while( x > y );
return x - y;

x and y are member variables of type volatile int. I do not understand this construct.

I found the code stub here: https://gist.github.com/r-lyeh/cc50bbed16759a99a226. I guess it is not guaranteed to be correct or even work.

sleske
  • 81,358
  • 34
  • 189
  • 227
Luca
  • 10,458
  • 24
  • 107
  • 234
  • 9
    Are you sure you transcribed that correctly (especially, the semicolon at the end of the first line)? – Jerry Coffin Nov 11 '15 at 06:01
  • 4
    If `x > y` your code will fall into an infinite loop. – mazhar islam Nov 11 '15 at 06:03
  • 1
    without the additional `;` this statement would work, although it being strange – Marged Nov 11 '15 at 06:03
  • 1
    Absolutely transcribed correctly. I am quite confused. The variables `x` and `y` are declared volatile – Luca Nov 11 '15 at 06:04
  • 14
    @Luca On what environment ist that code running? Is it some embedded controller where x and y are changed from outside (e.g. through an interrupt handler)? – Andreas Fester Nov 11 '15 at 06:13
  • @Andreas: clever approach – Marged Nov 11 '15 at 06:16
  • @Andreas It is some implement of a ring buffer class that I found online here: https://gist.github.com/r-lyeh/cc50bbed16759a99a226 – Luca Nov 11 '15 at 06:23
  • 4
    @Luca It isn't correctly transcribed: original has `while(x-y); return y-x;` and also only `y` is volatile, not both. But that doesn't change the accepted answer. – Saul Nov 11 '15 at 07:27
  • @Saul: Ah yes, sorry I was trying a few things and it was 4 am here! – Luca Nov 11 '15 at 07:48
  • 1
    @rakeb.mazharul: How can you know? He said x and y are declared volatile. How you know what the writers Hardware might be operating on x and y? People don't forget there are more fields then just desktop application development... – dhein Nov 11 '15 at 09:05
  • 3
    While the below answers are possibly true, I find it more probable that the whole thing is a joke. It is named after the city of R'lyeh in the Lovecraft mythos. This city built by principles so insane that merely looking at it will make you mad. – Stig Hemmer Nov 11 '15 at 09:43
  • I'd suggest changing the question title to a more descriptive/less generic one; I tried myself but couldn't come up with a convincing title without "giving away" the answer. – UncleZeiv Nov 11 '15 at 14:22
  • 2
    FWIW, the code you linked, described as a `// very simple circular buffer`, is totally broken. – MooseBoys Nov 11 '15 at 17:29
  • 1
    Just to chime in with everyone else, this code is hopelessly broken in a long list of ways. I wouldn't suggest trying to learn from it or understand it. – David Schwartz Nov 11 '15 at 19:47
  • 1
    If you _must_ write a spin loop (bad practice at all levels above microcode/microcontrollers), at least have a body that calls `yield()`. – keshlam Nov 11 '15 at 22:38
  • 1
    The code given has a race-condition. It's possibly acceptable, but `int result = x - y; while(result > 0) result = x - y; return result;` would be necessary to ensure that the difference between x and y that triggered the loop ending was the same as that returned. – Jon Hanna Nov 12 '15 at 10:15

4 Answers4

62

Since x and y have been declared as volatile, the programmer expects that they will be changed from outside the program.

In that case, your code will remain in the loop

 while(x>y);

and will return the value x-y after the values are changed from outside such that x <= y. The exact reason why this is written can be guessed after you tell us more about your code and where you saw it. The while loop in this case is a waiting technique for some other event to occur.

therainmaker
  • 4,253
  • 1
  • 22
  • 41
  • 5
    Maybe worth noticing is that if `x - y` is being executed `x > y` may be true again (changed in the meantime). Isn't it safer to read it into a local variable before doing all these checks and calculations? – Caramiriel Nov 11 '15 at 08:07
  • 12
    @Caramiriel: you are right and it’s even worse. Since `x` and `y` are distinct variables, they are updated and read individually and declaring them `volatile` doesn’t change that. Thus, a thread may change both variables together in a way that is not intended to change the outcome of `x>y` but this loop may read an intermediate state where only one variable has been updated yet and `x>y` spuriously evaluates to false. Of course, this never happens during debugging… – Holger Nov 11 '15 at 08:36
  • 4
    Note that this will be bad for CPU usage though, as it instantly re-iterates possibly for seconds. Honestly the current thread should sleep at least a millisecond, which would already cut CPU usage drastically. – Stephan Bijzitter Nov 11 '15 at 11:04
  • 16
    However, the code is rather fine if applied on microcontroller. A chip may be too small to have proper threads or scheduling via timer interrupts. Not sure to what extent this approach is used, but this is definitely possible. – u354356007 Nov 11 '15 at 11:25
  • 5
    It also may be fine if the rest of the code provides the additional invariants needed to prove that it is safe. As a general rule, multithreading code using `volatile` and no other synchronization tools should be regarded with suspicion. The actual rules for what `volatile` does are very specific and nuanced, but many developers believe it does things it doesn't actually do. (or rather it actually does do them on their particular compiler in that particular case, so they never learn otherwise!) – Cort Ammon Nov 11 '15 at 17:57
25

It seems

while( x > y );

is a spinning loop. It won't stop until x <= y. As x and y are volatile, they may be changed outside of this routine. So, once x <= y becomes true, x - y will be returned. This technique is used to wait for some event.

Update

According to the gist you added, it seems the idea was to implement thread-safe lock-free circular buffer. Yes, the implementation is incorrect. For example, the original code-snippet is

unsigned count() const {
    while( tail > head );
    return head - tail;
}

Even if tail becomes less or equal to head, it is not guaranteed that head - tail returns positive number. The scheduler may switch execution to another thread immediately after the while loop, and that thread may change head value. Anyway, there are a lot of other issues related to how reading and writing to shared memory work (memory reordering etc.), so just ignore this code.

Stas
  • 11,571
  • 9
  • 40
  • 58
  • I guess `while( x > y );` is not guaranteed to generate a spinning loop according to recommended practices for the target platform. For example IA32 has a `PAUSE` instruction, which all spinning loops are recommended to contain for performance reasons. But I guess there is no guarantee that a compiler will emit that instruction with the given source. – kasperd Nov 11 '15 at 21:56
20

Other replies have already pointed out in detail what the instruction does, but just to recap that, since y (or head in the linked example) is declared as volatile changes made to that variable from a different thread will cause the while loop to finish once the condition has been met.

However, even though the linked code example is very short, it's a near perfect example of how NOT to write code.

First of all the line

while( tail > head );

will waste enormous amounts of CPU cycles, pretty much locking up one core until the condition has been met.

The code gets even better as we go along.

buffer[head++ % N] = item;

Thanks to JAB for pointing out i mistook post- with pre-increment here. Corrected the implications. Since there are no locks or mutexes we obviously will have to assume the worst. The thread will switch after assigning the value in item and before head++ executes. Murphy will then call the function containing this statement again, assigning the value of item at the same head position. After that head increments. Now we switch back to the first thread and increment head again. So instead of

buffer[original_value_of_head+1] = item_from_thread1; 
buffer[original_value_of_head+2] = item_from_thread2;

we end up with

buffer[original_value_of_head+1] = item_from_thread2; 
buffer[original_value_of_head+2] = whatever_was_there_previously;

You might get away with sloppy coding like this on the client side with few threads, but on the server side this could only be considered a ticking time bomb. Please use synchronisation constructs such as locks or mutexes instead.

And well, just for the sake of completeness, the line

while( tail > head );

in the method pop_back() should be

while( tail >= head );

unless you want to be able to pop one more element than you actually pushed in (or even pop one element before pushing anything in).

Sorry for writing what basically boils down to a long rant, but if this keeps just one person from copying and pasting that obscene code it was worth while.

Update: Thought i might as well give an example where a code like while(x>y); actually makes perfect sense. Actually you used to see code like that fairly often in the "good old" days. cough DOS. Was´nt used in the context of threading though. Mainly as a fallback in case registering an interrupt hook was not possible (you kids might translate that as "not possible to register an event handler").

startAsynchronousDeviceOperation(..);

That might be pretty much anything, e.g. tell the hardisk to read data via DMA, or tell the soundcard to record via DMA, possibly even invoke functions on a different processor (like the GPU). Typically initiated via something like outb(2).

while(byteswritten==0); // or while (status!=DONE);

If the only communication channel with a device is shared memory, then so be it. Wouldnt expect to see code like that nowadays outside of device drivers and microcontrollers though. Obviously assumes the specs state that memory location is the last one written to.

Syren Baran
  • 444
  • 2
  • 8
  • Thanks for the voice of reason; the intent is clear to a human, but compilers are not human... – Matthieu M. Nov 11 '15 at 13:26
  • That was a very nice deconstruction. Thank you for that. +1 – Luca Nov 11 '15 at 14:59
  • How exactly would the item from thread 1 end up in the position of item from thread 2 if thread 1's `head++` is evaluated _before_ the context switch and thread 2's is implied to not be? A more subtle issue, however, would be both threads getting the original value of `head` (as without an atomic variable or lock `head++` could take multiple operations, generally involving caching the value before incrementing it and then returning the cached value), thus causing one or the other item to end up in the first position and the second position to retain its previous value. – JAB Nov 11 '15 at 16:47
  • Of course, even with atomicity you could end up with item 1 in position 2 and item 2 in position 1, but if you're writing a concurrent data structure you usually have to live with that. Even using locks could produce that result. – JAB Nov 11 '15 at 16:51
  • @JAB head at e.g 0. T1 increments head. Switch to T2. T2 increments head. T2 assigns value using heads value of 2. Switch to T1. T1 assigns its item using the same value of head = 2. So yes, its a very possible scenario. Obviously highly unlikely at any given operation, but repeat this operation often enough (as is typical for a shared memory construct on Server side) and you will end up with corrupted data. – Syren Baran Nov 11 '15 at 17:06
  • @SyrenBaran with post-increment, the returned value is determined before the increment itself, so T2 would have to increment head before T1 actually obtains head's value, meaning T2 would come first anyway. There would be another possible issue if head were not volatile where neither thread sees the other's update before doing the increment, but that would still result in both threads trying to write to position 1 rather than 2. If code used `++head`, however, then your situation would be possible. In fact, looking at your example, it seems you assumed pre-increment rather than post-increment. – JAB Nov 11 '15 at 17:11
  • @JAB Right, for some reason i assumed pre-increment. Thanks for poiting that out. Gonna correct that part later ( not that the implications regarding data corruption are any better in that case ). Might as well add an example where that type of coding is actually correct and usefull while i'm at it. – Syren Baran Nov 11 '15 at 17:23
7

The volatile keyword is designed to prevent certain optimisations. In this case, without the keyword, the compiler could unroll your while loop into a concrete sequence of instructions which will obviously break in reality since the values could be modified externally.

Imagine the following:

int i = 2;
while (i-- > 0) printf("%d", i);

Most compilers will look at this and simply generate two calls to printf - adding the volatile keyword will result in it instead generating the CPU instructions that invoke a counter set to 2 and a check on the value after every iteration.

For example,

volatile int i = 2;
this_function_runs_on_another_process_and_modifies_the_value(&i);
while(i-- > 0) printf("%d", i);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Olipro
  • 3,489
  • 19
  • 25
  • `this_function_runs_on_another_process_and_modifies_the_value(&i);` If you're doing multithreading/multiprocessing and don't have a proper memory fence or other way of guaranteeing order, you want `atomic`, not `volatile` (well, assuming you care about the order; in some cases relaxed ordering is fine). In C++(11), the compiler can still reorder operations on volatile variables, it just can't remove any loads or stores. – JAB Nov 11 '15 at 16:54
  • Indeed, but the original question was calling for an explanation of `volatile` - For any actual application requiring synchronisation on values, obviously the `atomic` keyword is appropriate. – Olipro Nov 11 '15 at 17:49