7

An embedded project I'm working on requires reading a specific location in memory, but the value of that memory location is not needed. Currently I am reading the volatile variable into a dummy variable as in foo1() below, but I am curious about the method in foo2().

void foo1(void) {
    volatile uint32_t *a = (volatile uint32_t *)0xdeadbeef;
    volatile uint32_t discard = *a;
}
void foo2(void) {
    volatile uint32_t *a = (volatile uint32_t *)0xdeadbeef;
    *a;
}

See the dissassembly (compiled with gcc 4.7.2 and -O3):

  foo1:
movl      0xdeadbeef, %eax
movl      %eax, -0x4(%rsp)
ret
  foo2:
movl      0xdeadbeef, %eax
ret

The method in foo2() seems to work, but I want to know if it is guaranteed to work and isn't a side effect of the compiler version and optimizations I am using.

Tim
  • 4,790
  • 4
  • 33
  • 41
  • short answer is Yes, the C standard guarantees this. Also the `volatile` qualifier for `discard` is not needed. – M.M Dec 30 '15 at 03:36

3 Answers3

2

It's standard in C, but not C++. See: https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Volatiles.html

BUT, you certainly don't need to do the volatile write to discard. These will probably all compile to the same as foo2:

  • use foo1, but remove the volatile qualifier from discard; or
  • use foo2, but use !*a or *a + 0 instead of *a. The value must be accessed in order to evaluate the expression, even in C++
Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
  • Even without the negation, the expression has to be evaluated. That is exactly what `volatile` is for. – too honest for this site Dec 30 '15 at 03:24
  • The value _is_ accessed [even under c++] with just `*a;` [_because_ it's `volatile`] and `foo2` generates less code than `foo1` on my setup. If you build _without_ `volatile`, both functions become nops – Craig Estey Dec 30 '15 at 03:25
  • @CraigEstey: No wonder `foo1` generates more code, as it has to to perform both: the read and the write. – too honest for this site Dec 30 '15 at 03:28
  • @Olaf Yes. I generated all forms (and did x64 [by mistake] until I saw the code difference, then added -m32) and even did clang and c++/clang++. All are about the same. I've seen the `*a;` before in other R/T code for just the same purpose (e.g. must access the word but don't care about value)--I believe I've even written code for similar H/W. In this instance, the memory area [page] policy would need to be [as you mentioned] non-cached [for this code to be useful]. Code would be the same but I wonder if intent is clearer with `(void) *a;`? – Craig Estey Dec 30 '15 at 03:43
  • @Olaf, in C++ you can say `volatile uint32_t &x = *a;`, which does _not_ do the volatile read... so it's not really clear that a bare *a would, because it's not really necessary to convert the l-value *a into its r-value. – Matt Timmermans Dec 30 '15 at 04:15
  • @MattTimmermans: This question is about C. References are a C++ featrure and the question does not even go into that direction. They are completely other beasts; e.g. you cannot even use them like that because they have no value on their own (basically). Please stick to the question; this is no discussion or comparison forum. – too honest for this site Dec 30 '15 at 07:02
  • @CraigEstey: If you enable compiler warnings (always do!) and use a half-way modern compiler, you should get one for `*a;`: "value of expression not use" or so. The cast is the correct way to tell the compiler that you don't care and he has to shut up. And yes, I think this makes the intend quite clear to the reader, too. Much more than the assignment or just writing `*a;`. But either way, you should comment your code and mention **why** you do this. – too honest for this site Dec 30 '15 at 07:07
  • @Olaf I always do -Wall [and a few more] and 30% of all code I write is comments. You'll get a warning on foo1 [I wouldn't have coded it that way], but foo2 is clean. In practice, I always wrap device port access in access functions [possibly inline] such as read_device/write_device so that I can add tracing with an #ifdef. It helps a great deal if the H/W is still under development. With the trace, I'm able to prove to logic designer that the device was programmed correctly [and they need to fix their verilog/RTL] – Craig Estey Dec 30 '15 at 10:54
2

The keyword volatile tells the compiler an object might change outside the scope of the normal (i.e. visible by the compiler) program flow. Therefore, the compiler performs this access in general. The last sentence refers to how the access is performed, e.g. byte-read, unaligned reads, etc.

Futhermore, the compiler must execute all accesses to such objects in the order given by the program flow. Note, however, that it may reorder accesses to non-volatile objects freely and the underlying hardware might think different, too (the compiler might not know that).

The compiler might still optimize accesses to volatile objects which exist and are modified only in the visible code. This is true for local variables where the address is not taken (there might be other situations), as these cannot be reached outside the scope. For the pointers you use, this is not true, as the compiler does not know about the object they point to.

To drop the result of an expression without compiler warning, just cast it to void:

volatile uint32_t *vi = ...;
(void)*vi;            // this still might be optimized if vi is local

(If the target is read-only, add const.)

See the gcc documentation for details on volatile accesses. An implementation which complies to the C standard has to provide this information.

Also note that the underlying hardware still might reorder the accesses, unless the memory area uses a strictly ordered/non-cached access policy. This is typical for memory-mapped peripheral registers. If a cache/MMU is used, the areas might have to be set up accordingly.

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
  • It is important to realise that we're talking about objects having qualifications, as opposed to l-values having qualifications. Does the object pointed to by `(volatile uint32_t *)0xdeadbeef` indeed have a `volatile` qualification? We don't know, because we can't see it's declaration. Perhaps it points to `discard`, declared below it, in `foo1`... Perhaps not. – autistic Dec 30 '15 at 04:59
  • @Seb: This is an integer cast to a pointer. The conversion is implementation defined. This is the typical way to provide the address of a memory mapped peripheral register at a specific address. What do you mean with "declaration of the object"? There is none. The only declaration is that of the pointer `vi` (resp. `a` in the question). About "perhaps it point to ...": It better should not, because that would add tons of other problems for the compiler. It is the responsibility of the programmer to make sure there is no aliasing (and it would not make any sense either). – too honest for this site Dec 30 '15 at 06:59
  • Depending upon your implementation, there may indeed be an object declared using the `volatile` qualifier that has such an address. It's also possible that there might be an object declared without the qualifier that has such an address, and a compiler might be able to determine that the object really isn't `volatile`-qualified... What happens, then? "What constitutes an access to an object that has volatile-qualified type is implementation-defined." – autistic Dec 30 '15 at 09:27
  • @Seb: I'm quite confident I mentioned that in the third paragraph. Still it would be aliasing (through the pointer and the original name). Remember we are talking about setting an explicit address to a compiler-allocated object which is reachable through its name already. If you have such code, you should review your code; this is definitively not the clean way. I write embedded code and have never seen the need for such hacks. – too honest for this site Dec 30 '15 at 16:44
1

There is no guarantee that dereferencing a volatile object causes a read access, see ISO 9899:2011 §6.7.3 ¶7:

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.

134) A volatile declaration may be used to describe an object corresponding to a memory-mapped input/output port or an object accessed by an asynchronously interrupting function. Actions on objects so declared shall not be “optimized out” by an implementation or reordered except as permitted by the rules for evaluating expressions.

In practice, implementations of the C programming language commonly define unary * to constitute an access to an object, thus guaranteeing that *a is causing a read access to a volatile variable a.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • 1
    Seems like 134 there would constitute a guarantee. It's explicitly saying actions (like dereferencing the value) on the `volatile` can't be optimized out. – ShadowRanger Dec 30 '15 at 01:53
  • 2
    @ShadowRanger Read above, “What constitutes an access to an object that has volatile-qualified type is implementation-defined.” – fuz Dec 30 '15 at 01:54
  • Ah, sorry. Clearly need more sleep. Stupid "implementation defined" nonsense. :-) – ShadowRanger Dec 30 '15 at 01:54
  • The importance of footnote 134 shouldn't be undermined. `(volatile uint32_t *)0xdeadbeef` is not a declaration. I don't know why this answer got -1; it is technically correct... I guess the truth hurts, sometimes? – autistic Dec 30 '15 at 04:54
  • @Seb: `(volatile uint32_t *)0xdeadbeef` certainly is not (and the `volatile` qualifier in the cast is actually unnecessary), but the variable declaration actually is and that is the relevant part. The usage is actually uncommon, as there are normally `#define`s for the peripherals (be it `struct *` or `uintN_t *`. Anyway; the footnote refers to the variable declaration which is actually the part in question here. And the last paragraph is pretty unclear. `*` dereferences a pointer. Not sure how that is implementation-specific. (Btw the answer got 2 DVs). – too honest for this site Dec 30 '15 at 06:51
  • @Olaf At the time I commented, it had only had one DV. It might be wise to consider the previous point (p6, IIRC?) which makes a distinction between objects declared with the `volatile` qualifier and lvalues that treat an object as though it has a `volatile` qualifier... – autistic Dec 30 '15 at 09:23
  • @Seb Where am I talking about declarations? The relevant point still stands: “What constitutes an access to an object that has volatile-qualified type is implementation-defined.” Please elaborate on how my answer is invalid. – fuz Dec 30 '15 at 11:53
  • 1
    @Seb And ¶6 only considers the case that a volatile object is accessed through a pointer to a non-volatile object. How does this apply? – fuz Dec 30 '15 at 12:04
  • 1
    @FUZxxl I never said your answer was invalid. In fact, I UV'd it because I like it. What p6 says is perhaps not relevant here, though it does strengthen the need for the object to be defined using the volatile qualifier, as opposed to changing the qualiification using an lvalue. – autistic Dec 30 '15 at 12:19
  • @Seb But ¶6 does not concern this scenario. – fuz Dec 30 '15 at 12:36
  • @FUZxxl Are you trying to argue with me? Because if so, you're not doing such a great job at it... I already said that I agree, p6 is not relevant, but that it does introduce the term "object defined with a volatile-qualified type" as distinct from lvalue expressions used to access it. – autistic Dec 30 '15 at 13:06
  • @Seb Why don't you say that in the first place? – fuz Dec 30 '15 at 13:27