0

In the following code snippet, an interrupt routine uses one of many arrays for its execution. The array used is selected synchronously, not asynchronously (it will never change while the ISR is executing). On a single core microcontroller (this question assumes an STM32L496 if the architecture is important), is the volatile specifier required in the declaration of foo?

int a[] = {1, 2, 3};
int b[] = {4, 5, 6};
int * foo; //int * volatile foo? int volatile * volatile foo?

main(){
    disable_interrupt();
    foo = a;
    enable_interrupt();
    ...
    disable_interrupt();
    foo = b;
    enable_interrupt();
}

void interrupt(){
    //Use foo
}

My assumption is that the volatile specifier is not required because any caching of the value of foo will be correct.

EDIT:

To clarify, the final answer is that volatile or some other synchronisation is required because otherwise writes to foo can be omitted or reordered. Caching is not the only concern.

lenguador
  • 476
  • 5
  • 9

1 Answers1

1

volatile stops the compiler optimizing it, forcing the compiler

  • To always read the memory, not a cached value from a register
  • To not move things before, or after the volatile read/write

On a complex CPU (e.g. x86), it is possible for the CPU to re-order operations before, or after a volatile access.

It is typically for memory-mapped-io, where regions of memory, are actually devices, and can change (even on a single core CPU), without visible cause.

The mechanism for C++11 is to use std::atomic to change a value which may occur on different threads of execution.

With a single core, the code will safely modify the value, and store it. If you use volatile, then it will be written to the memory point, before the interrupts are enabled.

If you don't use volatile, then the code may still have the new value in a register before it is used in the interrupt.

int * volatile foo;

Describes that foo can change, but the values it points to, are stable.


int volatile * volatile foo

Describes foo can change, and the things it points to can also change. I think you want int * volatile foo;

Update

For those who doubt that volatile is a compiler barrier.

From the standard n4296

Accessing an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression (or a sub-expression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects. When a call to a library I/O function returns or an access to a volatile object is evaluated the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by the volatile access may not have completed yet.

and

From cppreference cv object

volatile object - an object whose type is volatile-qualified, or a subobject of a volatile object, or a mutable subobject of a const-volatile object. Every access (read or write operation, member function call, etc.) made through a glvalue expression of volatile-qualified type is treated as a visible side-effect for the purposes of optimization (that is, within a single thread of execution, volatile accesses cannot be optimized out or reordered with another visible side effect that is sequenced-before or sequenced-after the volatile access. This makes volatile objects suitable for communication with a signal handler, but not with another thread of execution, see std::memory_order). Any attempt to refer to a volatile object through a non-volatile glvalue (e.g. through a reference or pointer to non-volatile type) results in undefined behavior.

These seem to concur, that there is a compiler barrier, but some of the side effects of interacting with the volatile object may not have completed. For the single core processor, it appears to be a suitable mechanism if C++11 atomics are not available.

From : C++ standard : n4296

We have :-

Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated.

From this I understand, there is a happens-before relationship for any operation with a side-effect.

Access to volatile objects are evaluated strictly according to the rules of the abstract machine

From this I understand, that there are rules (which maybe opaque).

Accessing an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression (or a sub-expression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects. When a call to a library I/O function returns or an access to a volatile object is evaluated the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by the volatile access may not have completed yet.

From this I understand that access to volatile (and a few other things), create a side effect, which stops the compiler from re-ordering statements near a volatile access.

mksteve
  • 12,614
  • 3
  • 28
  • 50
  • "To not move things before, or after the register." - That's fatally wrong. – too honest for this site Oct 01 '17 at 17:44
  • thanks fixed to what i meant – mksteve Oct 01 '17 at 17:46
  • That's what I already read. And it is wrong. Volatile accesses are not compiler barriers. – too honest for this site Oct 01 '17 at 17:47
  • `Describes that foo can change, but the values it points to, are stable.` stable??? it only declares the pointer volatile, and the referenced obbject no. `volatile * volatile foo;` declares them both volatile. `volatile` does not stop any optimisations (this is a very common misunderstanding), just forces the storage location operations. – 0___________ Oct 01 '17 at 18:14
  • @PeterJ_01. I have re-read my sources, and it looks that volatile does block the compiler from optimizing through the volatile access. Please ask a question directly if you want a full answer. – mksteve Oct 01 '17 at 18:34
  • @Olaf, I am not sure you are correct. usage of `volatile` is an observable side-effect, limiting compiler re-ordering. – mksteve Oct 01 '17 at 18:36
  • 1
    @mksteve you just repeat most popular myths about the volatile keyword. plus for you for explaining lack of atomicity and coherency. For the rest do some experiments yourself: https://godbolt.org/g/emifkE – 0___________ Oct 01 '17 at 19:46
  • Thanks for the godbolt example, but it shows how the volatile within an expression can occur with non-deterministic reads (you can't even tell which `x` is done by which load), not whether the compiler is able to optimize through an expression. e.g. https://godbolt.org/g/QPV5Rq - z will be written before x is read. – mksteve Oct 01 '17 at 20:00
  • 1
    @mksteve As stated, you should read about the exact meaning of `volatile`. Expecially about the common missconception about its atomicity and barrier behaviour. And I'd appreciate a reference to the standard defining "things". – too honest for this site Oct 01 '17 at 22:51
  • The statement on cppreference is flat out wrong and is not adhered to by any modern x86 compiler I know of. Compilers do nothing to prevent volatile accesses from being reordered, nor does any standard require them to. – David Schwartz Oct 01 '17 at 23:17
  • @DavidSchwartz [intro.execution] prevents sequenced volatile accesses (and other observable side effects) from being reordered w.r.t each other – Cubbi Oct 02 '17 at 22:18
  • @Cubbi Neither GCC nor clang put memory barriers between such accesses, permitting them to be freely re-ordered by the CPU, caches, and so on. Either you're wrong or the x86 compiler developers are wrong. (The crux is that there is no platform-neutral concept of what it means to observe an access to memory. Thus what it means to make such things observable behavior is entirely platform-specific.) – David Schwartz Oct 02 '17 at 22:20
  • @DavidSchwartz recall the actual meaning of volatile: you're controlling a piece of hardware using MMIO. If the compiler reorders two volatile writes, the airplane crashes. Of course if you're using MMIO, you have to disable caching for that VM page. – Cubbi Oct 02 '17 at 22:24
  • @Cubbi Right, so you have a platform-specific problem and need to use whatever platform-specific solution solves it for you. That might be `volatile` if the platform's documentation say that it's suitable for that purpose. Or it might be a special "atomic" type, or hand-coded assembly, or whatever else the platform specifically happens to require to solve this platform-specific hardware requirement problem. – David Schwartz Oct 02 '17 at 22:25
  • As a quality-of-implementation issue, the guys who made your compiler might make `volatile` do the right thing for this use case. But if it has very serious negative performance implications for other uses of `volatile` explicitly required by the standard, they probably won't and you then wouldn't be able to use `volatile` for that purpose. For example, `volatile` on x86 isn't sufficient to ensure writes aren't reordered, which might cause problems with hardware registers and so you need more than `volatile` on those platforms sometimes. – David Schwartz Oct 02 '17 at 22:26