9

Where does the standard define that a volatile variable can change undetected?

I've found two normative text which are about volatile:

intro.execution/7:

Reading an object designated by a volatile glvalue ([basic.lval]), modifying an object, calling a library I/O function, 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 (or a subexpression) in general includes both value computations (including determining the identity of an object for glvalue evaluation and fetching a value previously assigned to an object for prvalue evaluation) and initiation of side effects. When a call to a library I/O function returns or an access through a volatile glvalue is evaluated the side effect is considered complete, even though some external actions implied by the call (such as the I/O itself) or by the volatile access may not have completed yet.

Is this paragraph about the undetected change? Could the side effect mean this?


Or there is dcl.type.cv/5:

The semantics of an access through a volatile glvalue are implementation-defined. If an attempt is made to access an object defined with a volatile-qualified type through the use of a non-volatile glvalue, the behavior is undefined.

Is this paragraph about my question? What does "The semantics of an access through a volatile glvalue are implementation-defined" mean exactly? Can you give an example of different "semantics of an access"?


And there is dcl.type.cv/6, which is about my question, but it is just a note:

[ Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object. See [intro.execution] for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. — end note ]

Passer By
  • 19,325
  • 6
  • 49
  • 96
geza
  • 28,403
  • 6
  • 61
  • 135
  • Are you asking just out of curiosity? Or is there some underlying reasons? If the latter, what is the *real* and *actual* problem you have? Perhaps you should ask more directly about that instead? – Some programmer dude Jul 23 '18 at 12:22
  • 1
    @Someprogrammerdude: I'd like to give an answer to my own previous [question](https://stackoverflow.com/questions/51472394/is-it-allowed-for-a-compiler-to-optimize-away-a-local-volatile-variable), and I'd like to understand this issue better. So no real underlying programming problem, but out of curiosity, and to understand volatiles better: how and when can a volatile variable change "undetected". – geza Jul 23 '18 at 12:24
  • 1
    I'm pretty sure this and the previous question was extensively discussed on SO already. – Passer By Jul 23 '18 at 12:28
  • "The semantics of an access through a volatile glvalue are implementation-defined" is exceptionally unclear. But I think it is to be read regarding "The semantic descriptions in this document define a parameterized nondeterministic abstract machine.". Therefore I take it to mean that reading one volatile variable can change anything. Even the value of nonvolatile variables. As long as the behavior is exactly documented. – Johannes Schaub - litb Jul 23 '18 at 12:32
  • @PasserBy: maybe... I searched for it, and haven't a found a C++ specific answer. If I fully understood this issue, I may give an answer to my previous question, which starts with: "Yes, the compiler can optimize that function in that manner. But, ...". So not the usual answer, and I think that I'm right about it. But I'd like to undestand volatiles better, before I give that answer, because I may be wrong. – geza Jul 23 '18 at 12:32
  • Volatile basically means that the variable must not be optimized. Every read or write access has to assume that triggers some side effect and optimizing it would change the behavior. There are 2 use cases for it: 1) a memory mapped IO register. 2) A variable changed in a signal handler. – Goswin von Brederlow Jul 23 '18 at 12:38
  • Can't find it at the moment. There was a relevant discussion about whether compilers are allowed to optimize away a particular volatile access to a local variable, which is an exact dupe to your previous question. On one hand some claim obviously no, on the other, some claim any way of observing such access invokes UB and thus the compiler is free. – Passer By Jul 23 '18 at 12:39
  • 1
    [This](https://stackoverflow.com/questions/38230856/is-the-definition-of-volatile-this-volatile-or-is-gcc-having-some-standard-co) might be relevant. – Passer By Jul 23 '18 at 12:40
  • @pass I disagree based on OP's quotes. It would rely on implementation defined behavior but not UB. If the implementation defined that every read on a local volatile will increment the value of every static local variable of the enclosing function.. that is perfectly testable without undefined behavior. – Johannes Schaub - litb Jul 23 '18 at 12:42
  • The best candidate I can find so far is from [intro.abstract] *Accesses through volatile glvalues are evaluated strictly according to the rules of the abstract machine.* – NathanOliver Jul 23 '18 at 12:44
  • @JohannesSchaub-litb I'm not going to debate that here, I'm just trying to find the discussion. FWIW my opinion is "ignore the standard, test your program and read the assembly". – Passer By Jul 23 '18 at 12:45
  • @PasserBy: I asked my previous question because of this [question](https://stackoverflow.com/questions/51471889/create-a-function-that-always-returns-zero-but-the-optimizer-doesnt-know). So, I think that the standard is important here, because it wouldn't be good if one always have to test what an implementation does with volatiles. It's better to have the guarantee the standard gives, then always checking whether the implementation behaves as we expect. – geza Jul 23 '18 at 12:49
  • @nathan that looks like a defect since it contradicts the other quote that semantics of accesses are implementation defined. EDIT: not necessarily a contradiction, since "Certain aspects and operations of the abstract machine are described in this document as implementation-defined". – Johannes Schaub - litb Jul 23 '18 at 12:59
  • @JohannesSchaub-litb I don't think so. Earlier in the section is also has *Certain aspects and operations of the abstract machine are described in this document as implementation-defined (for example, sizeof(int)). These constitute the parameters of the abstract machine. Each implementation shall include documentation describing its characteristics and behavior in these respects.5 Such documentation shall define the instance of the abstract machine that corresponds to that implementation (referred to as the “corresponding instance” below).* – NathanOliver Jul 23 '18 at 13:04
  • For the full text see: https://timsong-cpp.github.io/cppwp/intro.abstract – NathanOliver Jul 23 '18 at 13:04
  • @nathan yes, agreed. – Johannes Schaub - litb Jul 23 '18 at 13:06
  • [Found it](https://stackoverflow.com/questions/46994763/is-it-legal-to-optimize-away-stores-construction-of-volatile-stack-variables/47043127#47043127). That was difficult. In particular, the [chat room](https://chat.stackoverflow.com/rooms/158717/discussion-on-answer-by-nicol-bolas-is-it-legal-to-optimize-away-stores-construc) holds the discussion. Wait, you were there... – Passer By Jul 23 '18 at 13:10
  • @PasserBy: thanks, I'll read that in the evening. If it is a duplicate indeed, please feel free to close/vote to close my question(s). Whoa :) Now I know why I had some déjà vu feeling when I was writing my previous question :) – geza Jul 23 '18 at 13:13
  • @PasserBy: now I've checked that question. Very similar, but I don't think that's a duplicate. My example has a read, that question doesn't have one. And, as far as I understand, the accepted answer contradicts the current most [upvoted answer](https://stackoverflow.com/a/51472691/8157187). – geza Jul 23 '18 at 17:50
  • @GoswinvonBrederlow "_There are 2 use cases for it_" There are a lot of uses of volatile, as it prevent optimizations, guarantees ordering of computing, allows meaningful performance measurements, write the values of variable in their normal form (according to the ABI), treat the variables as possibly changed, by reading them back according to the ABI... there are many use cases for volatile. – curiousguy Jul 26 '18 at 03:37
  • @curiousguy Those all sounds like absuing a volatile variable to change something else. Like using a volatile variable as a barrier to guarantee order of computation. Better use the right construct for the right case. – Goswin von Brederlow Jul 26 '18 at 09:57
  • @GoswinvonBrederlow Why don't you tell us what is the right construct? – curiousguy Jul 26 '18 at 20:29
  • E.g. for a barrier use a barrier. For performance measurments make meaningfull testcases that run longer than 100 cpu cycles. Test actual real-live data instead of artificial test cases made to produce the result you want. And so on. – Goswin von Brederlow Jul 31 '18 at 14:15

2 Answers2

7

The key here is "changes in the state of the execution environment."

The execution environment is what's outside your program. That might include an OS, filesystem, screen, etcetera. It is generally unpredictable. You cannot assume that if you write a 0 to a file, that the file will not be overwritten by another process with a 1.

volatile variables are logically part of that execution environment. As far as C++ is concerned, the environment can enumerate them, read them and write them, just like files. And this can happen without your program knowing.

On the flip side, your implementation realizes the link between your program and its execution environment, so it does have some idea about what might happen. If it has some kind of private RAM disk implementation, then it might know that certain filenames are not externally visible in the OS filesystem. And it might know that volatile int i lives in a CPU register, so that it can't be accessed via memory mappings. That's all allowed by the C++ standard. It just talks about the execution environment in general terms, the implementation must be more precise. That is what "implementation-defined semantics" means.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I don't think there is anything that would make a plain `volatile int i` into anything different from a plain int in a sane compiler. At least in gcc you have to use an extension and declare the variable as asm register to make it a cpu register. What I mean is that compilers will have special extension and syntax to declare a variable to be a cpu register and don't just magically go by the name or make every volatile int the special cpu register. So real live "lives in a CPU" variables will look different in the source. – Goswin von Brederlow Jul 23 '18 at 12:59
  • 1
    @GoswinvonBrederlow Not CPU registers. IO hardware registers. These are totally different things. No one says `volatile` maps a variable onto a CPU register (we used to have the `register` keyword for that, but it is defunct for many years). – n. m. could be an AI Jul 23 '18 at 14:24
  • 1
    @GoswinvonBrederlow and of course it is extremely easy to see that a volatile variable is very different from a regular variable for any sane compiler. It's enough to look at the assembly output. See e.g. [this](https://godbolt.org/g/QJUoLi) – n. m. could be an AI Jul 23 '18 at 14:28
  • @n.m.: I wasn't specifying either type; "register" in this context just meant "doesn't live in the usual address space where C++ pointers can point to". As for "what does GCC do", the question is tagged language-lawyer so you need to consider all implementation variations. This question is particularly implementation-dependent because it discusses the external execution environment, which is widely varying. – MSalters Jul 23 '18 at 14:37
  • 1
    @MSalters `"register" in this context just meant "doesn't live in the usual address space where C++ pointers can point to". ` An IO hardware register absolutely positively does live in the usual address space where C++ pointers can point to. That's the whole point of memory mapped IO. Otherwise it wouldn't be called "memory mapped". – n. m. could be an AI Jul 23 '18 at 14:39
  • @n.m.: You may want to peek at Intel's x86 manual. Or ARM's co-processor interface. The whole reason we talk about memory-mapped I/O is to distinguish it from non-memory-mapped I/O. – MSalters Jul 23 '18 at 14:40
  • @MSalters An implementation normally wouldn't place a volatile variable in a CPU register, because it would defeat the purpose of `volatile`. Doubly so for a static storage duration volatile variable (an automatic volatile variable is rather useless, and a static storage duration variable normally cannot live in a register not addressable by normal pointers, because then you cannot interoperate sanely with any other language on the same platform. An implementation that doesn't interoperate with anything is allowed by the standard, but who would use one?) – n. m. could be an AI Jul 23 '18 at 14:45
  • @MSalters I'm very familiar with the x86 manual. I don't see how non-memory-mapped IO is related to the question at hand. If an implementation is crazy enough to assign a volatile variable to an IO port, then it *can* be accessed by the hardware. If you mean a CPU register, then surprise, those too can be accessed by means external to a program, though not by direct memory access (kernel modules, debuggers, in-circuit taps, whatever). Unless the implementation has total control of the entire ecosystem and can guarantee that no such thing can ever exist, it must respect volatile here too. – n. m. could be an AI Jul 23 '18 at 15:00
  • While your answer absolutely makes sense, I cannot "connect" it to the standard. According to the quote, "modifying an object ... changes in the state of the execution environment". So, any non-volatile object is part of the exec. env. as well. Does this mean that the value any non-volatile object can change "undetected"? Hopefully not. – geza Jul 23 '18 at 17:44
  • @n.m. "_An implementation normally wouldn't place a volatile variable in a CPU register, because it would defeat the purpose of volatile_" It would not. The purpose is to disable optimisations, not to make the program less efficient. Putting volatile variables in register as often as non volatile variable would be extremely useful and sane, as long as the program cannot tell the difference (the register cannot have more visible bits of precision) – curiousguy Jul 24 '18 at 22:14
  • @curiousguy Sorry I don't understand a single word of what you are saying. One needs volatile variables to map I/O ports or otherwise make a memory address accessible to forces external to the program (henceforth "I/O hardware"). Disabling optimisations is not the purpose of volatile, it's the necessary condition for being able to communicate with I/O hardware correctly. Communication with I/O hardware is the purpose. Putting a variable in a register would defeat that purpose, because it's hard to map a CPU register to the outside world. – n. m. could be an AI Jul 24 '18 at 22:41
  • @n.m. "_otherwise make a memory address accessible to forces external to the program_" I/O ports is just one historical justification for volatile. They are inherently machine specific. There are portable uses of volatile in C. And an async signal is not exactly an "external force", it's C/C++ code, just invoked at un unpredictable point. "_Communication with I/O hardware is the purpose_" One notable purpose. – curiousguy Jul 24 '18 at 22:49
  • "_Putting a variable in a register would defeat that purpose_" OK then show me a case where *volatile variables in register as often as non volatile variable*, that is, treating volatile like non volatile for the purpose of register allocation, breaks a possible use of volatile. – curiousguy Jul 24 '18 at 22:50
  • @curiousguy There might be portable uses, ibut the compiler cannot know what use the programmer has in mind. I have no idea what I have to show. Global variables don't go to registers, ever, volatile or not. Compilers don't work this way and linkers don't work this way. I don't care whether the standard actually says that, it's just true. – n. m. could be an AI Jul 25 '18 at 05:05
  • @n.m. "_volatile or not._" So you appear to just agree with me on that one. There is no need for a special case. – curiousguy Jul 25 '18 at 05:33
  • I have a problem understanding what you claim exactly – n. m. could be an AI Jul 25 '18 at 05:35
  • @n.m.: Given a global variable `unsigned x, arr[10];` a compiler could replace `for (int i=0; i<10; i++) x+=arr[i];` with `unsigned register temp=x; for (int i=0; i<10; i++) temp+=arr[i]; x=temp;`, effectively keeping `x` in a register for the duration of the loop. Only if `x` were `volatile` would that be disallowed. – supercat Aug 05 '18 at 21:52
1

volatile is just a request to the compiler asking it to reload the variable from memory for each access. It is intended for 2 common use cases:

  • a variable can be changed by a different thread (or even a different program which could be given write access to that memory zone), or by kernel mode code (for example by a special driver)
  • this variable represents a physical memory register, mainly used in kernel mode programming, or on OS having no notion of user/kernel mode like good old MS/DOS.

Once you know that, the different quotes from the standard all make sense.

Reading an object designated by a volatile glvalue ([basic.lval]), ... are all side effects, which are changes in the state of the execution environment.

Reading a hardware register might have an impact on the underlying system, that is the reason why it is said to be an observable side effect.

The semantics of an access through a volatile glvalue are implementation-defined. If an attempt is made to access an object defined with a volatile-qualified type through the use of a non-volatile glvalue, the behavior is undefined.

If you use a non volatile pointer to access a volatile hardware register, the compiler could cache the previous value and not execute the physical access.

[ Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object. See [intro.execution] for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. — end note ]

Some implementation could reserve a memory zone for special low level io ports operations. In that case, the combination of the volatile specifier and of that special memory zone addresses could be required to validate the transformation or normal memory access operation with special io operations.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252