Is `*(volatile T*)0x1234;` guaranteed to translate into read instruction?
Asked Answered
P

2

13

When working with hardware it is sometimes required to perform a read from a specific register discarding the actual value (to clear some flags, for example). One way would be to explicitly read and discard the value such as:

int temp = *(volatile int*)0x1234; // 0x1234 is the register address
(void)temp;                          // To silence the "unused" warning

Another way that seem to work is simply:

*(volatile int*)0x1234;

But this doesn't seem to obviously imply the read access, yet it seems to translate to one on compilers I checked. Is this guaranteed by the standard?

Example for ARM GCC with -O3: https://arm.godbolt.org/z/9Vmt6n

void test(void)
{
    *(volatile int *)0x1234;
}

translates into

test():
        mov     r3, #4096
        ldr     r3, [r3, #564]
        bx      lr
Padlock answered 6/12, 2018 at 19:44 Comment(11)
I think you could have written a very similar question based on writing to a port or register. The dereference of the address by itself implies neither reading nor writing.Baziotes
@TimRandall Hence the question, as it seems that even most aggressive optimizations are translating it into a reading instruction.Padlock
It might be helpful to list the compilers and/or hardware you've tested this on. It's an interesting questionBaziotes
Updated with specific examplePadlock
You might find Using volatile in embedded C development interesting.Nordic
+𝟷 I like to see questions from experienced guys sometimes even I don't understand them very well (:Gannon
Pretty much, yes. Because it is volatile. Without volatile, it is flagged as warning: statement with no effect [-Wunused-value]. To be totally clear, you could do: (void) *(volatile int *)0x1234;Chapel
I believe anything that refers to a specific address by number (eg 0x1234) is UB by standard. However, this is a nearly universally defined compiler extension, so what the standard says is somewhat irrelevant.Hughey
@Hughey Not in embedded systems where hardware device port addresses are fixed/hard coded constants.Chapel
Yes, it is an extension commonly defined by embedded compilers. Therefore any use of it is implementation-defined, not standard defined.Hughey
relevant discussion on this from the C++ sideEnvoi
G
8

C 2018 6.7.3 8 says:

An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3.…

Since *(volatile int*)0x1234; is an expression referring to an object with volatile-qualified type, evaluating it must access the object. (This presumes that 0x1234 stands for a valid reference to some object in the C implementation, of course.)

Per C 2018 5.1.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).

Per C 2018 6.5 1:

An expression is a sequence of operators and operands that specifies computation of a value, or that designates an object or a function, or that generates side effects, or that performs a combination thereof.

Thus, an expression specifies computation of a value. Paragraph 5.1.2.3 4 tells us that this evaluation is performed by the abstract machine, and 6.7.3 8 tells us the actual implementation performs this evaluation that the abstraction machine performs.

One caveat is that what constitutes “access” is implementation-defined. “Access” as defined by the C standard includes both reading and writing (C 3.1 1), but the C standard is unable to specify that it means reading from or writing to some particular piece of hardware.

To go further into language-lawyer, territory, C 6.3.2.1 2 tells us:

Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion.

Thus, since *(volatile int*)0x1234; is an lvalue, by dint of the * operator, and is not the operand of the listed operators, it is converted to the value stored in the object. Thus, this expression specifies the computation of the value that is stored in the object.

Gordongordy answered 6/12, 2018 at 20:13 Comment(0)
E
3

The gcc documentation on volatile tells us that what consititues a volatile access is implementation defined:

C has the concept of volatile objects. These are normally accessed by pointers and used for accessing hardware or inter-thread communication. The standard encourages compilers to refrain from optimizations concerning accesses to volatile objects, but leaves it implementation defined as to what constitutes a volatile access. The minimum requirement is that at a sequence point all previous accesses to volatile objects have stabilized and no subsequent accesses have occurred. Thus an implementation is free to reorder and combine volatile accesses that occur between sequence points, but cannot do so for accesses across a sequence point. The use of volatile does not allow you to violate the restriction on updating objects multiple times between two sequence points.

This is backed up by C11 section 6.7.3 Type qualifiers p7:

An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.134) What constitutes an access to an object that has volatile-qualified type is implementation-defined.

The gcc document goes on to specify how volatile works for gcc, for the case similar to your says:

A scalar volatile object is read when it is accessed in a void context:

volatile int *src = somevalue;
*src;

Such expressions are rvalues, and GCC implements this as a read of the volatile object being pointed to.

Envoi answered 6/12, 2018 at 20:39 Comment(3)
The last quote is the most interesting, as it actually answering "yes" to the original question for GCC.Padlock
Interestingly, "access" is a defined term in the standard: "to read or modify the value of an object." I'm therefore disinclined to take the implementation-definedness of volatile accesses as absolute (which would moot the earlier part of paragraph 6.7.3/7). In fact, I'm not at all sure how to reconcile that definition with the implementation-definedness provision at the end of 6.7.3/7.Disincline
At least GCC seems to have all its bases covered, though. It provides and documents behavior in this area that seems to be just what the standard would require if it we ignored the implementation-definedness.Disincline

© 2022 - 2024 — McMap. All rights reserved.