I've seen constructs like the following to write to memory-mapped I/O.
*((volatile unsigned int *)0xDEADBEEF) = 0x00;
But is this guaranteed to be a volatile access?1
I started to think about this after I've noticed following change in the C standard regarding cast operators:
The C11 spec says following:
N1570, § 6.5.4, ¶ 5:
Preceding an expression by a parenthesized type name converts the value of the expression to the named type. This construction is called a cast.104) [...]
N1570, footnote 104):
- A cast does not yield an lvalue. Thus, a cast to a qualified type has the same effect as a cast to the unqualified version of the type.
In C17 this has changed to:
N2310, § 6.5.4, ¶ 5:
Preceding an expression by a parenthesized type name converts the value of the expression to the unqualified version of the named type. This construction is called a cast.108) [...]
N2310, footnote 108):
- A cast does not yield an lvalue.
Both versions also have:
N2310, § 6.5.3.2, ¶ 4:
The unary * operator denotes indirection. [...]; if it points to an object, the result is an lvalue designating the object. If the operand has type "pointer to type", the result has type "type". [...]
So for C17 it should be clear: the above shown code should be the same as *((unsigned int *)0xDEADBEEF) = 0x00;
, i.e., the volatile qualifier is dropped and thus this wouldn't be a volatile access, although we have the volatile
in the cast expression.
For C11 I am not sure: if we take the footnote as obligatory, then it shouldn't be a volatile access either, because "a cast to a qualified type has the same effect as a cast to the unqualified version of the type"; but without that footnote and just the text in (§ 6.5.4, ¶ 5) in mind, I would say it should be a volatile access – or are there some other paragraphs in the spec which make it clear that this should not be a volatile access?
For the sake of completeness: following should be guaranteed to be a volatile access:
volatile unsigned int *mem = (unsigned int *)0xDEADBEEF;
*mem = 0x00;
1Assuming that a write to an object of volatile-qualified type is defined as an access to that object; see following:
N1570, § 6.7.3, ¶ 8:
[...] What constitutes an access to an object that has volatile-qualified type is implementation defined.
The two versions of the standard are essentially saying the same thing in two different ways, i.e. that a cast effectively results in a unqualified type.
What you seem to be confused by here is that you're not actually casting to a qualified type. You're casting to a pointer (which is unqualified in this case) to a qualified type. The pointer itself is not qualified, but what it points to is.
So this:
*((volatile unsigned int *)0xDEADBEEF) = 0x00;
Is considered volatile access, as the constant 0xDEADBEEF
is converted to a pointer (which is not volatile) to a volatile unsigned int
, and that pointer is subsequently dereferenced to access the volatile object.