5
volatile int vfoo = 0;
void func()
{
    int bar;
    do
    {
        bar = vfoo;  // L.7
    }while(bar!=1);
    return;
}

This code busy-waits for the variable to turn to 1. If on first pass vfoo is not set to 1, will I get stuck inside.

This code compiles without warning. What does the standard say about this?

  • vfoo is declared as volatile. Therefore, read to this variable should not be optimized.
  • However, bar is not volatile qualified. Is the compiler allowed to optimize the write to this bar? .i.e. the compiler would do a read access to vfoo, and is allowed to discard this value and not assign it to bar (at L.7).
  • If this is a special case where the standard has something to say, can you please include the clause and interpret the standard's lawyer talk?
aiao
  • 4,621
  • 3
  • 25
  • 47
  • 1
    How would it "optimize the write to `bar`"? In order to do that, the value it's writing would have to be known in advance, but the value is the result of loading `vfoo`, which cannot be elided because `vfoo` is volatile. – R.. GitHub STOP HELPING ICE Nov 11 '19 at 13:36
  • "If on first pass vfoo is not set to 1, will I get stuck inside." How do you test that? – Lundin Nov 11 '19 at 14:38
  • 1) Correct. 2) Type of `bar` doesn't matter, compiler isn't allowed to optimize the read of `vfoo`. 3) No it is not a special case and 100% well-defined. – Lundin Nov 11 '19 at 14:45

3 Answers3

6

What the standard has to say about this includes:

5.1.2.3 Program execution

¶2 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. Evaluation of an expression in general includes both value computations and initiation of side effects. Value computation for an lvalue expression includes determining the identity of the designated object.

¶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).

¶6 The least requirements on a conforming implementation are:

  • Accesses to volatile objects are evaluated strictly according to the rules of the abstract machine.
  • ...

The takeaway from ¶2 in particular should be that accessing a volatile object is no different from something like calling printf - it can't be elided because it has a side effect. Imagine your program with bar = vfoo; replaced by bar = printf("hello\n");

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • "_no different from something like calling `printf`_" more like calling `scanf` here – curiousguy Nov 13 '19 at 03:55
  • 1
    @curiousguy: Either one has side effects that can't be elided; that was the point. `scanf` is a closer semantic analog to a volatile load, but I was kinda trying to avoid semantic arguments and focus on the higher level concept of "has a side effect". – R.. GitHub STOP HELPING ICE Nov 13 '19 at 12:43
2

volatile variable has to be read on any access. In your code snippet that read cannot be optimized out. The compiler knows that bar might be affected by the side effect. So the condition will be checked correctly.

https://godbolt.org/z/nFd9BB

0___________
  • 60,014
  • 4
  • 34
  • 74
  • Is this just gcc being nice; Or 100% fool-proof required by standard. – aiao Nov 11 '19 at 11:03
  • As I wrote - it can't optimize the non volatile variable as it became side effect prone as well. – 0___________ Nov 11 '19 at 11:14
  • @P__J__ Can you cite the C standard on that? I’m no expert in C but in C++ the situation would be quite different. – Konrad Rudolph Nov 11 '19 at 11:16
  • 1
    @KonradRudolph 100% disagree. Can you cite C++ standard? – 0___________ Nov 11 '19 at 11:22
  • @P__J__ I No, I think you are right but I’m currently not confident. I *think* the chain of reasoning here is: 8.5.18/1 assignments are sequenced-after the RHS and LHS value computation. RHS value computation in this case is a side-effect because it reads a volatile; and value computation of the comparison is sequenced-after the assignment (6.8.1/8 & /9). However, I cannot find why the assignment of `bar` cannot be elided, although this seems like it *should* be obvious. Need more coffee. – Konrad Rudolph Nov 11 '19 at 11:53
  • @KonradRudolph the object was contaminated and cannot be optimized out. Otherwise, it would break the most important rule - about the observable behaviour of the program. – 0___________ Nov 11 '19 at 11:56
  • @P__J__ But “the object was contaminated” isn’t a phrase you’ll find in the standard. The whole point of this question is to show whether equivalent wording does exist. As I’ve said in my previous comment this should be obvious but I can’t (now) find it in the C++ standard. – Konrad Rudolph Nov 11 '19 at 12:01
  • C11 (6.7.3/6): _If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined._ – Gerd Nov 11 '19 at 13:07
  • @Gerd there is no reference here. Here you have an example of referencing: https://godbolt.org/z/KexH_4 – 0___________ Nov 11 '19 at 13:28
  • 1
    @Gerd: You're getting confused by the word referencing. Storing the value loaded from the volatile object somewhere else is not referencing it. The language you quoted is about illegally accessing a volatile object via an overlaid (via type punning) object of the wrong, non-volatile type. E.g. `*(int *)&vfoo`. – R.. GitHub STOP HELPING ICE Nov 11 '19 at 13:45
0

However, bar is not volatile qualified.

Variable bar is used to hold a value. Do you care about the value stored in it, or do you care about that variable being represented exactly according to the ABI?

Volatile would guarantee you the latter. Your program depends on the former.

Is the compiler allowed to optimize the write to this bar?

Of course. Why would you possibly care whether the value read was really written to a memory location allocated to the variable on the stack?

All you specified was that the value read was tested as an exit condition:

        bar = ...
    }while(bar!=1);

.i.e. the compiler would do a read access to vfoo, and is allowed to discard this value and not assign it to bar (at L.7).

Of course not!

The compiler needs to hold the value obtained by the volatile read enough time to be able to compare it to 1. But no more time, as you don't ever use bar again latter.

It may be that a strange CPU as a EQ1 ("equal to 1") flag in the condition register, that is set whenever a value equal to 1 is loaded. Then the compiler would not even store temporarily the read value and just EQ1 condition test.

Under your hypothesis that compilers can discard variable values for all non volatile variables, non volatile objects would have almost no possible uses.

curiousguy
  • 8,038
  • 2
  • 40
  • 58