Search code examples
ccastingvolatile

volatile access through cast with volatile-qualified type


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):

  1. 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):

  1. 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.


Solution

  • 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.