I work with microcontrollers where there are occasionally machine registers that have actions that occur when a register is read. Yes, that's not a typo, there's lots of registers that cause actions when they are written, but in a few cases if you read a register, something happens.
The most common instance of this is a UART receive register hooked up to one end of a FIFO; for example let's say there is RXDATA
. When you read RXDATA
it pulls one byte out of the FIFO and the next time you read RXDATA
it will get the next byte.
Is there enough information in volatile
to get the compiler to understand that there might be side effects from a read?
Example fragment in C:
#include <stdint.h>
#include <stdbool.h>
volatile uint8_t RXDATA;
// there is some mechanism for associating this with a known hardware address
// (either in linker information, or in some compiler-specific attribute not shown)
// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_1()
{
const uint8_t rxdata = RXDATA;
return (rxdata & 1) && ((rxdata & 0x80) == 0);
}
// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_2()
{
return (RXDATA & 1) && ((RXDATA & 0x80) == 0);
}
// Check that bit 0 is 1 and bit 7 is 0.
bool check_bits_3()
{
const bool bit_0_is_1 = RXDATA & 1;
const bool bit_7_is_0 = (RXDATA & 0x80) == 0;
return bit_0_is_1 && bit_7_is_0;
}
If I ignore the C standard and pretend that a compiler does exactly what I think I am asking it to do (DWIM), then my intuition is that these three functions have different behavior:
RXDATA
once, so we pull out one byte of the FIFO and then do some math on it.RXDATA
either once or twice (because &&
has short-circuit behavior), doing math directly on the register value, so we might either pull out one or two bytes from the FIFO, and this has incorrect behavior.RXDATA
twice, pulling two bytes from the FIFO, so this is incorrect behavior.Whereas if RXDATA
isn't volatile
then presumably all three of the above implementations are equivalent.
Does the C standard require the compiler to interpret volatile
in this case in the same way I am looking at it? If not, how can a hardware register be handled properly in C?
Is there enough information in volatile to get the compiler to understand that there might be side effects from a read?
Yes.
The C language formal definition of a side effect actually targets this very scenario. C11 5.1.2.3:
Accessing a
volatile
object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.
Regarding what the compile is allowed to optimize, C11 5.2.3.4:
In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a
volatile
object).
In plain English, this means that any form of access, read or write, to a volatile object, is considered a side effect and a compiler is not allowed to optimize away side effects.
...then my intuition is that these three functions have different behavior
Indeed they have. This is why coding standards such as MISRA-C forbids us to mix volatile
variable access together with other things in the same expression. In the UART scenario, doing so might cause loss of status flags which would be a severe bug.
Robust programs read/write to volatile variables on a single line and do all other necessary arithmetic in separate expressions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With