0

I know that I should use the volatile keyword to tell the compiler not to optimize memory read\write to variables. I also know that in most cases it should only be used to talk to non-C++ memory.

However, I would like to know if I have to use volatile when holding a pointer to some local (stack) variable.

For example:

//global or member variable
/* volatile? */bool* p_stop;

void worker()
{
    /* volatile? */ bool stop = false;
    p_stop = &stop;
    while(!stop)
    {
        //Do some work
        //No usage of "stop" or p_stop" here
    }
}

void stop_worker()
{
    *p_stop = true;
}

It looks to me like a compiler with some optimization level might see that stop is a local variable, that is never changed and could replace while(!stop) with a while(true) and thus changing *p_stop while do nothing.

So, is it required to mark the pointer as volatile in such a case?

P.S: Please do not lecture me on why not to use this, the real code that uses this hack does so for a (complex-to-explain) reason.

EDIT:

  1. I failed to mention that these two functions run on different threads. The worker() is a function of the first thread, and it should be stopped from another thread using the p_stop pointer.

  2. I am not interested in knowing what better ways there are to solve the real reason that is behind this sort of hack. I simply want to know if this is defined\undefined behavior in C++ (11 for that matter), and also if this is compiler\platform\etc dependent. So far I see @Puppy saying that everyone is wrong and that this is wrong, but without referencing a specific standard that denoted this.

I understand that some of you are offended by the "don't lecture me" part, but please stick to the real question - Should I use volatile or not? or is this UB? and if you can please help me (and others) learn something new by providing a complete answer.

Community
  • 1
  • 1
ZivS
  • 2,094
  • 2
  • 27
  • 48
  • 5
    For threading synchronization, use `std::atomic`. `volatile` is unrelated to thread. – Jarod42 Apr 06 '17 at 20:47
  • `volatile` is *correlated* with threading; it's not unrelated, but you're right, it's a different thing. – Jason S Apr 06 '17 at 20:48
  • Like I wrote, please don't lecture me on why not to use `volatile`, I know how to use the new synchronization mechanism – ZivS Apr 06 '17 at 20:50
  • if `//work` call code that may use `p_stop`, compiler cannot optimize to `while (true)`. – Jarod42 Apr 06 '17 at 20:50
  • `//work` does not use it. I edited the question – ZivS Apr 06 '17 at 20:52
  • Just to see if I understand it right: doesn't this code imply that there can be only one worker at a time, because `p_stop` can point only to one (local) variable? Any other workers could not be controlled any more, so above scenario cannot work in multiple threads running different workers... – Stephan Lechner Apr 06 '17 at 21:04
  • But why are you concerned about compiler optimizing away `stop` and making your cycle infinite? You cycle IS infinite, since you never change `stop` inside the cycle. And you never mentioned any other reason `stop` can change. – AnT stands with Russia Apr 06 '17 at 21:07
  • @StephanLechner, assume that `void work()` is running in one thread, and another thread calls `stop_worker()`. So this program has a main thread and a worker thread for example. So yes - only one worker – ZivS Apr 06 '17 at 21:07
  • 1
    @ZivS: So, now we are talking about different threads. Yes, if `stop`can be changed by different thread, it should be `volatile`. – AnT stands with Russia Apr 06 '17 at 21:08
  • 3
    No, it really should not be `volatile`, it should be *atomic*, which is a completely different thing. – Puppy Apr 06 '17 at 21:09
  • 1
    Down-voters - please explain why you do so – ZivS Apr 06 '17 at 21:13
  • @ZivS Questions about "predicting results of UB" tend to get downvotes. (I've not downvoted.) – HolyBlackCat Apr 06 '17 at 21:14
  • @HolyBlackCat, Even if this is the case, how can an OP know that this is UB if the downvoters don't comment... – ZivS Apr 06 '17 at 21:16
  • *"Please do not lecture me on why not to use this"* made me feel that you're aware that atomics do exist and don't want to use them for some reason. And if one knows about atomics, they probably also know why atomics were added and why they should be used in multithreading instead of volatiles. – HolyBlackCat Apr 06 '17 at 21:18
  • I didn't vote (yet), but you admit in the last sentence that you're actually trying to do something else that you don't dare mention yet. That means that answers answering the question as posted are likely to be met with "Oh but in the *real code*, bla bla bla so your answer doesn't help". – M.M Apr 06 '17 at 21:20
  • For example, as posted, it makes no difference whether the compiler optimizes `stop` out or not. In fact that is a good optimization. Adding `volatile` anywhere would make no difference (unless the program has UB already of course). Now, tell me that actually it does matter because the *real code* calls `stop_worker` in a different thread or something... – M.M Apr 06 '17 at 21:21
  • You are right, I do know why I should use atomics, but I can't use them for reasons that I did not explain, this might be a reason for some people to down vote...either way, this question obviously created an interesting controversy – ZivS Apr 06 '17 at 21:22
  • 2
    Also, you repeatedly say "Please don't lecture me, I know bla bla" but then say "how can an OP know this is UB?". Lol – M.M Apr 06 '17 at 21:26
  • 1
    Perhaps the *very important information* that those two functions will be called from different threads should be part of the question?!? (downvoted due to this) – Daniel Jour Apr 06 '17 at 21:47
  • @DanielJour, I updated the question, Indeed I failed to mention this was referring to threads , I thought it was clear – ZivS Apr 06 '17 at 22:03

4 Answers4

7

I simply want to know if this is defined\undefined behavior in C++ (11 for that matter)

Ta-da (from N3337, "quasi C++11")

Two expression evaluations conflict if one of them modifies a memory location [..] and the other one accesses or modifies the same memory location.

§1.10/4

and:

The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior. [..]

§1.10/21

You're accessing the (memory location of) object stop from different threads, both accesses are not atomic, thus also in no "happens before" relation. Simply put, you have a data race and thus undefined behavior.

I am not interested in knowing what better ways there are to solve the real reason that is behind this sort of hack.

Atomic operations (as defined by the C++ standard) are the only way to (reliably) solve this.

Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
  • Thank you very much. This answers my question. – ZivS Apr 06 '17 at 22:31
  • So something I don't understand now, If it is undefined behavior to access memory from two threads, how does volatile work at all? for example, is the answer here UB http://stackoverflow.com/questions/72552/why-does-volatile-exist ? – ZivS Apr 06 '17 at 22:40
  • Yes, that's UB. volatile makes accesses to the object in question behave strictly as the standard says. Basically, this "disables" the [as if rule](http://en.cppreference.com/w/cpp/language/as_if) of the standard which allows aggressive optimizations. And for signal handlers and template metaprogramming ;) – Daniel Jour Apr 06 '17 at 23:20
  • Does the answer change if we go from C++ to C? – Jason S Apr 06 '17 at 23:31
  • 1
    @JasonS No, relevant parts of the standard: §5.1.2.4/4 and §5.1.2.4/25 from [N1570](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) ("quasi C11" AFAIK). – Daniel Jour Apr 06 '17 at 23:40
  • what about the `std::atomic` items... is there a C equivalent? – Jason S Apr 07 '17 at 05:08
  • 1
    @JasonS There's the [`stdatomic.h`](http://en.cppreference.com/w/c/atomic) header. – Daniel Jour Apr 07 '17 at 07:40
4

So, is it required to mark the pointer as volatile in such a case?

No. It's not required, principally because volatile doesn't even remotely cover what you need it to do in this case. You must use an actual synchronization primitive, like an atomic operation or mutex. Using volatile here is undefined behaviour and your program will explode.

volatile is NOT useful for concurrency. It may be useful for implementing concurrent primitives but it is far from sufficient.

Frankly, whether or not you want to use actual synchronization primitives is irrelevant. If you want to write correct code, you have no choice.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • depends on the underlying architecture... single-core microcontrollers with a strong memory model should be able to deal ok with just `volatile`, and without heavyweight synchronization primitives. – Jason S Apr 06 '17 at 21:07
  • 3
    Not even remotely. The C++ Standard defines what is acceptable in the C++ language. Whether or not the hardware your implementation might or might not be implemented with might give stronger guarantees is irrelevant. – Puppy Apr 06 '17 at 21:08
  • 1
    interesting... I think you are right in theory, even though in practice certain C/C++ compilers may provide memory semantics consistent with a single-core processor's sequential-only memory access. Do you have any information on the subject? I found this article: http://preshing.com/20120930/weak-vs-strong-memory-models/ "As I demonstrated previously, you must still specify the correct memory ordering constraints, if only to prevent compiler reordering." but I can't find where he gives an example of required memory ordering constraints for the compiler, even on strong hardware. – Jason S Apr 06 '17 at 21:13
  • 1
    It's tricky to create concrete examples because the compiler simply emits machine code that does what you expect it to most of the time but is broken in subtle and non-deterministic ways. Have a look at http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf. It's slightly out of date now, but the situation in this case hasn't really changed- C++11 mostly made it possible to be correct with atomics, rather than changing how volatile is totally broken (for this usage) – Puppy Apr 06 '17 at 21:21
2

P.S: Please do not lecture me on why not to use this,

I am not sure what we are supposed to say. The compiler manages the stack, so anything you are doing with it is technically undefined behavior and may not work when you upgrade to the next version of the compiler.

You are also making assumptions that may be different than the compiler's assumptions when it optimizes. This is the real reason to use (or not use) volatile; you give guidance to the compiler that helps it decide whether optimizations are safe. The use of volatile tells the compiler that it should assume that these variables may change due to external influences (other threads or special hardware behavior).

So yes, in this case, it looks like you would need to mark both p_stop and stop with a volatile qualifier.

(Note: this is necessary but not sufficient, as it does not cause the appropriate behaviors to happen in a language implementation with a relaxed memory model that requires barriers to ensure correctness. See https://en.wikipedia.org/wiki/Memory_ordering#Runtime_memory_ordering )

Jason S
  • 184,598
  • 164
  • 608
  • 970
  • Sorry, I did not understand from your answer if my example requires volatile or not – ZivS Apr 06 '17 at 20:54
  • Could you elaborate on why `The compiler manages the stack, so anything you are doing with it is technically undefined behavior`? – ZivS Apr 06 '17 at 20:56
  • With `volatile`, a compliant compiler can no longer replace the loop by `while (true)`, but the change from an other thread might be not visible. – Jarod42 Apr 06 '17 at 20:59
  • Good question. I'm not an expert in the C standard, so I wouldn't be able to find the appropriate clauses. But here's one thing: Just because you declare a local variable within a function doesn't mean it actually goes onto the stack. The compiler may choose to put it into registers and never put it on the stack. So the address of a register isn't a meaningful concept. (Though `&stop` may force it to put it on the stack.) – Jason S Apr 06 '17 at 21:00
  • @Jarod42, How can it not be visible if the compiler doesn't change to `while`? – ZivS Apr 06 '17 at 21:01
  • No, this is complete bollocks. `volatile` can't help you in this situation. – Puppy Apr 06 '17 at 21:02
  • @ZivS `volatile` semantics affect only the compiler's optimization. They do NOT enforce any behavior with respect to the underlying machine memory model (barriers and so forth, which are present on multicore processors) – Jason S Apr 06 '17 at 21:02
  • @Puppy, do you have some reference to back you on that? – ZivS Apr 06 '17 at 21:04
  • Well, if `volatile` is enough, why does `std::atomic` exist? Just for instance. See http://en.cppreference.com/w/cpp/language/memory_model. – Puppy Apr 06 '17 at 21:05
  • @Puppy Of course it's not enough: there will be some problems with operations being non-atomic. But it's better than nothing if OP can't use atomics. – HolyBlackCat Apr 06 '17 at 21:06
  • There won't be "some problems", it's completely undefined behaviour and outright illegal. – Puppy Apr 06 '17 at 21:06
  • It's neither necessary nor sufficient to use volatile here. – M.M Apr 06 '17 at 21:24
1

This question simply cannot be answered from the details provided.

As is stated in the question this is an entirely unsupported way of communicating between threads.

So the only answer is:

Specify the compiler versions you're using and hope someone knows its darkest secrets or refer to your documentation. All the C++ standard will tell you is this won't work and all anyone can tell you is "might work but don't".

There isn't a "oh, come on guys everyone knows it pretty much works what do I do as the workaround? wink wink" answer.

Unless your compiler doesn't support atomics or suitably concurrent mechanisms there is no justifiable reason for doing this. "It's not supported" isn't "complex-to-explain" so I'd be fascinated based on that code fragment to understand what possible reason there is for not doing this properly (other than ancient compiler).

Persixty
  • 8,165
  • 2
  • 13
  • 35
  • 1
    It quite clearly can be answered. The pointer shouldn't be `volatile` because `volatile` won't even remotely solve the problem. – Puppy Apr 06 '17 at 21:11
  • Dan, at the moment there are 2 answers telling the opposite about this, so I'm looking for a convincing one, and if the answer is no, I will be glad to explain the case – ZivS Apr 06 '17 at 21:11
  • 2
    @puppy That depends on the platform though. It's not actually wrong to exploit non-standard compiler behaviours. It's just (probably) totally unnecessary in this case. – Persixty Apr 06 '17 at 21:14
  • @ZivS They're saying largely the same as me in different ways. This isn't in portable C++ how you should do this. Any answer where you haven't specified your platform is there for bodging or guessing. Read this on volatile referring to x86. It will work there but by accident. http://stackoverflow.com/questions/7504646/visual-c-volatile – Persixty Apr 06 '17 at 21:21
  • @ZivS Yes, please explain the case. Post another question saying you think you need to do it because X but what are the alternatives. I bet there are loads and all compliant. – Persixty Apr 06 '17 at 21:22
  • 1
    @DanAllen: It's not wrong to do so, if your compiler explicitly guarantees that it is safe and guarantees to do so for all time. But then, that's not a C++ question involving the use of the C++ language construct `volatile`. – Puppy Apr 06 '17 at 21:26
  • 1
    @puppy It's `volatile` but not as we know it! (Jim). For example on MSVC targetting x86 this will work. Acquire and release semantics are implicit and `volatile` will ensure no reordering. But within the scope of the question as asked it remains undefined. – Persixty Apr 06 '17 at 21:36
  • @DanAllen, I have a "reactor" class that has a single thread, and a message queue of functions that should be invoked from that thread. the functions that are invoked might have a reference to the reactor's instance, and might decide for some reason (which may or may not make sense) to destroy that instance. In this case, the calling thread is actually calling the destructor of the class that holds it (in which there is a join() on that thread) which will cause a deadlock...continue in next comment – ZivS Apr 06 '17 at 22:22
  • I need to be able to close the thread despite such scenario and allow the destructor to complete, so I call `stop_worker` and call `detach()` on that thread, and inside the thread I make sure that it does not use any members of the class that holds it. It is a mandatory that I deal with such a case and not enter a deadlock. Since I can't use any class members after `detach()` I use the stack variable and its pointer. Do you agree it is not simple to explain this scenario? I am not really interested in help or other ways to solve this, rather just understand a corner case I thought of – ZivS Apr 06 '17 at 22:24
  • I don't see any of that as a reason to not use `std::atomic`. Indeed you seem to be trying to synthesise it out of `volatile`. – Persixty Apr 06 '17 at 22:37
  • @DanAllen I have to keep it as a local variable either way. If my stack holds an atomic boolean instead of a simple bool, how would I acess it from a different thread? hold a pointer to an atomic? then I'm back to my original question - should it be volatile so the compiler won't optimize it out? – ZivS Apr 06 '17 at 22:44
  • @ZivS If that's how you want to do it. Yes a pointer to an atomic. Also make the pointer atomic and you'll get the semantics you want. The compiler isn't allowed to optimize the atomic out. That's exactly what atomics are for. Inter-thread coordination. Atomic does in a standards compliant way what you're trying to do with `volatile`. Here's a starter http://stackoverflow.com/questions/31978324/what-exactly-is-stdatomic – Persixty Apr 07 '17 at 06:06
  • @ZivS Just ask a question "How do I do this properly with atomics". I will say I don't like passing out a pointer to a stack variable to another process in any solution. If `worker()` throws an exception the stack will unexpectedly unwind and the pointer will be invalidated. It is more normal to create a worker object that you can manage the lifetime of or pass in a reference to the stop marker. – Persixty Apr 07 '17 at 06:21