This should say 2010. I believe much of it is out of date, as C11 <i>does</i> have a memory model, and <i>does</i> provide both atomics and barriers. Many, if not most, uses of volatile should probably be replaced by atomics.<p><a href="https://en.cppreference.com/w/c/atomic" rel="nofollow">https://en.cppreference.com/w/c/atomic</a>
In terms of "Using volatile too much" I found a comment along the lines of "Not sure why this has to be volatile, but it doesn't work without it" and the answer was "There is a race condition and volatile slows down one path enough to make it go away."<p>Yuck.
You really should just use volatile for device drivers when accessing IO space with side-effects. Do not use volatile to build your own synchronization primitives.
I think the title and parts of the article are misleading. Using volatile will <i>never</i> make a correct program incorrect. It cannot "break" a correct implementation.<p>It should not be overused, because as the article mentions it makes for slower and more confusing code, but it's not quite something to be afraid of either.<p>It is slower to use volatile, and bad form
That note at the end about Linux is missing a link to the Documentation/volatile-considered-harmful.txt document. Basically, don't use volatile. Here, with your choice of formatting:<p><a href="https://github.com/torvalds/linux/blob/master/Documentation/process/volatile-considered-harmful.rst" rel="nofollow">https://github.com/torvalds/linux/blob/master/Documentation/...</a><p><a href="https://www.mjmwired.net/kernel/Documentation/volatile-considered-harmful.txt" rel="nofollow">https://www.mjmwired.net/kernel/Documentation/volatile-consi...</a><p><a href="https://www.kernel.org/doc/html/latest/process/volatile-considered-harmful.html" rel="nofollow">https://www.kernel.org/doc/html/latest/process/volatile-cons...</a>
The entire section on declarations can also be fixed by always binding type modifiers and quantifiers to the left. Rewriting the examples:<p><pre><code> int* p; // pointer to int
int volatile* p_to_vol; // pointer to volatile int
int* volatile vol_p; // volatile pointer to int
int volatile* volatile vol_p_to_vol; // volatile pointer to volatile int
</code></pre>
This method always starts with the most basic type, then adds modifiers sequentially. The modifier binds to everything left of it.
<p><pre><code> > "Side note: although at first glance this code looks like it fails to account for the case where TCNT1 overflows from 65535 to 0 during the timing run, it actually works properly for all durations between 0 and 65535 ticks."
</code></pre>
From example 1, ignoring device and setup-specifics what to do when TCNT1 overflows, it actually works properly for <i>all</i> ticks, both "first" and "second" are unsigned (therefore behaviour is defined), and the delta between them both is always between 0 and 65535, no matter what values they may have, and also correct in all cases.<p>E.g.:<p><pre><code> timeDelta = timeStampNow - timeStampLast = 0 - 65535 = 1</code></pre>
I've never had to use volatile in code. This was all very interesting!<p>For issue #5, a possible solution not mentioned could be to write inline assembly, no? It would keep the array non-volatile and should be portable.
Edit: Looks like the slides had an inaccuracy (see replies). Huh, looks like I learned something today :)<p>I think a good way of summarizing volatile is this slide from my parallel architectures class [1]:<p><pre><code> > Class exercise: describe everything that might occur during the
> execution of this statement
> volatile int x = 10
>
> 1. Write to memory
>
> Now describe everything that might occur during the execution of
> this statement
> int x = 10
>
> 1. Virtual address to physical address conversion (TLB lookup)
> 2. TLB miss
> 3. TLB update (might involve OS)
> 4. OS may need to swap in page to get the appropriate page
> table (load from disk to physical address)
> 5. Cache lookup (tag check)
> 6. Determine line not in cache (need to generate BusRdX)
> 7. Arbitrate for bus
> 8. Win bus, place address, command on bus
> 9. All caches perform snoop (e.g., invalidate their local
> copies of the relevant line)
> 10. Another cache or memory decides it must respond (let’s
> assume it’s memory)
> 11. Memory request sent to memory controller
> 12. Memory controller is itself a scheduler
> 13. Memory controller checks active row in DRAM row buffer.
> (May > need to activate new DRAM row. Let’s assume it does.)
> 14. DRAM reads values into row buffer
> 15. Memory arbitrates for data bus
> 16. Memory wins bus
> 17. Memory puts data on bus
> 18. Requesting cache grabs data, updates cache line and tags,
> moves line into exclusive state
> 19. Processor is notified data exists
> 20. Instruction proceeds
> * This list is certainly not complete, it’s just
> what I came up with off the top of my head.
</code></pre>
It's also worth mentioning that this assumes a uniprocessor model, so out-of-order execution is still possible which leads to complications in any sort of multithreaded or networked system (See #5, 6, 7, 8 in the OP article).<p>I think a lot of the confusion stems from the illusion that a uniprocessor + in-order execution model implies to programmers who have never dealt with system-level code. I think in the future, performant software will require a bit more understanding of the underlying hardware on the part of your average software developer -- especially when you care about any sort of parallelism. It doesn't help that almost all common CS curriculum ignores parallelism until the 3rd year or more.<p>[1] <a href="http://www.cs.cmu.edu/~418/lectures/12_snoopimpl.pdf" rel="nofollow">http://www.cs.cmu.edu/~418/lectures/12_snoopimpl.pdf</a> - the last 2 slides
Ironically volatile is just as bad in Java for different reasons. Frequently used for "lock free" synchronization, its usually actually worse than using locks because it can't be cached between cores. The variable is always loaded from main memory, which is usually much worse than holding a lock mutex in registers.
Err...volatile just tells the compiler not to cache the value in a register, that's it. If you don't understand volatile you really, really are not the kind of programmer who should even think about using it.