volatile Basetype *pointer;
Yes, but in this particular case, the problem is more like
{
char buffer[2] = { 0, 0 };
{
volatile char *const ptr = buffer;
// Use ptr to set up machine registers for DMA, but do not explicitly dereference it.
// Wait for DMA to complete
}
// Is the compiler allowed to assume buffer[0] == 0 and buffer[1] == 0 ?
}
The compiler sees a pointer to volatile contents of the buffer, but not it being dereferenced; does that mean that in the outer block, it has to assume the buffer may have changed? The aliasing rules and C++ standard in general is not something I want to pore through to find out (and I suspect the answer is
it's implementation defined or somesuch).
My suggested fix goes like this:
{
char buffer[2] = { 0, 0 };
{
// Use buffer to set up machine registers for DMA, but do not explicitly dereference it.
// Wait for DMA to complete
asm volatile ("": "+m" (*(char (*)[2])buffer));
}
// The compiler knows the memory (storage representation of the buffer array) may have changed,
// so it is not allowed to assume buffer[0] == 0 and buffer[1] == 0.
}
The inline assembly statement is the only way I know of how to ensure the compiler knows the data may have changed, without generating any extra code. (A call to an externally defined function that takes either buffer as a parameter would also work, but that would be extra code, even if the call simply returned immediately.)
Of course, this only works with GCC (and other C/C++ compilers that support the same inline assembly syntax; I think clang at least).
The simplest standards-compliant way is to mark the buffer volatile,
{
volatile char buffer[2] = { 0, 0 };
{
// Use buffer to set up machine registers for DMA, but do not explicitly dereference it.
// Wait for DMA to complete
}
// The compiler is not allowed to assume buffer[0] == 0 and buffer[1] == 0?
}
(or equivalently, the buffer content references using pointers that declare the pointed-to data volatile).
If the method โ the inner code block above โ was compiled in a different compilation unit, then there would be no problem, because the compiler would not be allowed to make any assumptions about the array contents across the method call.
However, when compiled in the same unit, so the compiler sees the full implementation, I am not certain even making the method parameter a pointer to volatile data suffices, because the volatility of the data may not propagate across the aliasing to the enclosing block. In other words, with the method having a pointer to volatile data as an argument, its body for sure would not be allowed to make any assumptions about the buffer contents, but whether the compilers propagate that to the caller, is ... questionable. In the past, this has been exactly the point where idiotic interpretations of the standard have occurred, leading to compiler behaviour that is opposite to what compiler users actually want, need, and assume.
You could say that it is obvious that the only thing that makes sense in practice is for the volatility to effectively do what the inline assembly does, tell the compiler that it no longer can make any assumptions about the contents of that buffer, but .. you know. Standards.
I do expect the standards committees to talk about this at some point, because this is so closely related to atomicity, barriers, aliasing, etc. they've waffled on for years. Something about invalidating compiler assumptions about exact contents of variable storage representation or memory ranges.