3

For the sake of this question, let us look at only reads on volatile variables. All the discussions I have read, the only conclusion is that multiple reads on the same variable declared volatile cannot be optimized out to a single effect.

But I believe that is a bit strict. Consider two reads on a variable, which do not have any side effect between them, or do not have read of any other volatile variable between them.

Now we know that the value in a volatile variable can change any time (without the compiler having a hint of it). But there is no way for the programmer to ensure that the change will happen between the two reads. This means that both the reads seeing the same value is a valid behavior for the program.

So can't the compiler enforce this behavior? Doing a single read and using the value twice.

For example

int foo(volatile int * x) {
    return *x + *x;
}

Can the compiler do a single read in this case?

I hope my query is clear.

Also I am assuming a system where the read itself doesn't have a side effect (like increment of a counter, or value changing with every read). Or do such systems exist?

I have looked at the assembly generated from gcc and clang and they do insert two reads even with maximum optimizations. My question is are they overly conservative?

Edit: To not complicate my question and avoid confusion with implementation defined order of evaluation of sub expressions, we can look at the example -

int foo(volatile int * x) {
    int a = *x;
    int b = *y;
    return a + b;
}

But I am also retaining the previous example because some answers and comments have referenced that.

Ajay Brahmakshatriya
  • 8,993
  • 3
  • 26
  • 49
  • Further more in this case, the evaluation order of operands for `+` are left implementation independent. Can the programmer make any assumption now? – Ajay Brahmakshatriya Aug 18 '17 at 11:34
  • Volatile is much like high level assembly. Store it in register on your own behalf if you really want to "optimize". – Tatsuyuki Ishi Aug 18 '17 at 11:35
  • @TatsuyukiIshi I agree! But the programmer will not see any changes in observable defined behavior. – Ajay Brahmakshatriya Aug 18 '17 at 11:36
  • Well if you have a box labeled "explosives" , can the postman treat it as non-explosive, since it doesn't explode that often? Of course he can!111!!! – joop Aug 18 '17 at 11:37
  • @joop but here it is guaranteed to not explode. It is guaranteed to behave like one of the acceptable behaviors. – Ajay Brahmakshatriya Aug 18 '17 at 11:39
  • It has to do two reads. Volatile accesses can't be optimized out. They're like IO. – Petr Skocik Aug 18 '17 at 11:42
  • The programmer can indeed not make any assumption about if `*x` is executed before or after `*x` :) Which obviously doesn't matter. It could have mattered if they were different variables. Example: "clear the SPI status register by reading the data register followed by a read of the status register". Then the code `something = *SPIDR + *SPISR` would be incorrect, since the order of evaluation is not only implementation-defined, but _unspecified_, meaning we can't know anything about it, and it may vary from case to case. – Lundin Aug 18 '17 at 11:46
  • @Lundin to avoid confusion, I have added sequence points between the reads. And in the case of single variable what would you say? – Ajay Brahmakshatriya Aug 18 '17 at 12:12
  • 1
    Possible duplicate of [Why is volatile needed in C?](https://stackoverflow.com/questions/246127/why-is-volatile-needed-in-c) It is clear, variable declared as `volatile` will always execute read command when used. Even if sequence is used! Period. End of story. – unalignedmemoryaccess Aug 18 '17 at 12:16
  • @tilz0R unfortunately it is not a duplicate. I have a very specific case in mind. I am not asking about use of volatile in general. I am asking about a specific case that is not covered in that question. – Ajay Brahmakshatriya Aug 18 '17 at 12:20
  • @AjayBrahmakshatriya Again, they are read 2 times as variable is used twice. If you are reading from memory where there are multiple possible acceses, 2 reads must be executed. – unalignedmemoryaccess Aug 18 '17 at 12:21

3 Answers3

4

Reading from a memory location can have a side effect. The program has to use more than standard C, of course. Reading can only have a side effect in a program that relies on implementation-defined behavior.

A common example would be reading from a memory-mapped peripheral. On many architectures, the main processor exchanges data with peripherals when data is read or written to particular ranges of memory locations. If a memory location is mapped to a peripheral, doing two reads sends two read requests to the peripheral. The peripheral may perform a non-idempotent operation on each read.

For example, reading a byte from a serial communication peripheral transfers the next byte in the peripheral's input queue each time. So if foo is called with the address of that serial peripheral's byte read register, then it pulls two successive bytes from the peripheral's read buffer. The compiler is not allowed to change the behavior to read only one byte.

Well, except that the behavior is undefined because there's no sequence point between the reads, and reading from a volatile is a side effect. A correct function would be

int foo2(volatile int *x) {
    int x1 = *x;
    int x2 = *x;
    return x1 + x2;
}

I'd expect most compilers to generate the same code for foo as for foo2 though.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254
  • Hence I added the para *Also I am assuming a system where the read itself doesn't have a side effect (like increment of a counter, or value changing with every read). Or do such systems exist?* Thank you for providing an example of such a system. – Ajay Brahmakshatriya Aug 18 '17 at 11:58
  • Now, how do these things fit into a standard? Do these all fit into some or other "implementation defined behaviors"? This kind of leads me to conclude that `volatile` should be used only when programming for a particular target. It does little change to the program in portable programs. – Ajay Brahmakshatriya Aug 18 '17 at 12:00
  • I agree I should have used two sequenced reads to not confuse with order of evaluation. – Ajay Brahmakshatriya Aug 18 '17 at 12:01
  • The variable could be shared among threads or processes in a portable way. – Jean-Baptiste Yunès Aug 18 '17 at 12:02
  • I'm not convinced there's a problem with `foo()` because `+` is symmetric, but if the operations using the values were not symmetric, the explicit read'n'save notation could easily be necessary. – Jonathan Leffler Aug 18 '17 at 12:09
  • @JeanBaptisteYunes if it is just a matter of threads, it should be fine to have a single read since the other thread(the one changing the value) could get scheduled after both the reads and that would have similar observable behaviour to a single read. – Ajay Brahmakshatriya Aug 18 '17 at 12:14
  • @Gilles and bunch of warnings. If parameter does not have to be volatile, do not make it volatile – 0___________ Aug 18 '17 at 12:24
  • @Jean-BaptisteYunès True, since C11. But even in C11, can you have an effectful *read* without using implementation-defined (or undefined) behavior? – Gilles 'SO- stop being evil' Aug 18 '17 at 12:34
  • @JonathanLeffler I don't know if there are platforms that actually do this, but there may be some kind of minimum separation between two reads that requires a sequence point. It isn't just about ordering. For example maybe the bus can issue multiple reads in the same clock cycle, but two reads from the same address need to be scheduled on different cycles or else the peripheral will only see one read, and the compiler only takes care of scheduling the reads properly if they're separated by a sequence point. – Gilles 'SO- stop being evil' Aug 18 '17 at 12:37
  • C can't express constraints on the time between two reads. If that's an issue, there is nothing that can be done in standard C to meet the hardware constraint. – Jonathan Leffler Aug 18 '17 at 12:40
  • @AjayBrahmakshatriya Before C11, `volatile` was only useful in one place in a strictly conforming program: for `volatile sig_atomic_t` values that are modified by a signal handler. Anything else that makes `volatile` useful required some implementation-defined way of sharing memory between multiple threads or with something outside the program. That doesn't mean that all code using `volatile` is target-specific: there are plenty of portable libraries that are designed to use in programs that are specific to environments with some form of concurrency. – Gilles 'SO- stop being evil' Aug 18 '17 at 12:40
  • @JonathanLeffler C can't express these constraints. That's the job of the compiler. But it would be legitimate for a compiler to take these constraints into account only at a sequence point, since the behavior of two volatile reads without an intervening sequence point is undefined. – Gilles 'SO- stop being evil' Aug 18 '17 at 12:41
  • We're headed on a collision course. I don't see how the compiler can know about those constraints because they can't be expressed in C; you seem to think it can know about them despite that. Let's stop the conversation here. We'll not make useful progress. – Jonathan Leffler Aug 18 '17 at 12:45
  • @Gilles I think this answer has cleared my queries to a significant extent. I will accept it. – Ajay Brahmakshatriya Aug 18 '17 at 12:57
3

Now we know that the value in a volatile variable can change any time (without the compiler having a hint of it). But there is no way for the programmer to ensure that the change will happen between the two reads. This means that both the reads seeing the same value is a valid behavior for the program.

You take a wrong conclusion from this. It may happen that the value does not change. But you don't know. And the compiler doesn't know.

If the compiler doesn't know why should the compiler be allowed to assume anything about changes? Therefore clearly: NO! The compiler mustn't combine any read access here.

That's a weird assumption. If you cannot ensure that all rabbits are white, how could it be a valid assumption that all are black?

It can also happen that the first read itself causes the value to change.

If you look at some hardware, it can be vital to do read accesses separately. Some timers or interrupt controllers clear some bits when they are read.

Also UARTs or Ethernet controllers might provide the whole receive buffer via one single address. You MUST read multiple times from the same address.

And the volatile keyword is the means to prevent the compiler from doing tricks.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Gerhardh
  • 11,688
  • 4
  • 17
  • 39
  • My argument was that if both reads can see the value, the programmer cannot complain if they are made to see the same value. The analogy would be if it is not guaranteed that all rabbits are white, I can make on rabbit black (forcefully). Since the standard doesn't say that all rabits should be white, it should be okay. – Ajay Brahmakshatriya Aug 18 '17 at 12:07
  • About the second half I agree. And that I mentioned in the question too. My side question was what are examples of systems where read has immediate side effects? Thanks for the examples. – Ajay Brahmakshatriya Aug 18 '17 at 12:08
  • @AjayBrahmakshatriya. No, your assumption is still wrong. If I cannot ensure all rabbits to be white, I might still ensure that there is only one single black rabbit around. The compiler (or you) do not have any right to make both rabbits black ones. You make invalid assumptions. Maybe the programmer does not know exactly which results will be read, the compiler definitely does not have any clue and its not the compiler's business to make some fancy guesses. – Gerhardh Aug 18 '17 at 12:25
2
int foo(volatile int * x) { return *x + *x; }

has to generate two reads. Volatile accesses can't be optimized out. They're like IO.

6.7.3p7:

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.


You can, of course, allow it to do just one read by introducing a temporary:

int foo(volatile int * x) { int x_cp = *x; return x_cp + x_cp; }

(Judging by the generated assembly, gcc takes the hint at optimization level -O1 or greater.)

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • It does not make any sense. Just do not declare parameter as volatile. I understand that you have done it for the reason. The compiler will complain about it as well. – 0___________ Aug 18 '17 at 11:54
  • @PeterJ It does make sense. If he wants foo to do exactly one read, for whatever reason, this is the way. Sure if this were a separately compiled function, he could simply remove the volatile qualifier, but without context, `foo` is potential candidate for inlining, in which case, without the volatile, there could be no read at all. – Petr Skocik Aug 18 '17 at 12:00
  • @PeterJ Exactly, and that's why if you want `foo` to always do exactly 1 read on x, the volatile qualifier isn't optional. – Petr Skocik Aug 18 '17 at 12:24
  • unfortunately I cant understand what you mean, as the sentence you wrote does no make any sense – 0___________ Aug 18 '17 at 12:25
  • If you do not need volatile do not use it, if you need don't abuse it. Simple – 0___________ Aug 18 '17 at 12:27
  • @PeterJ " Just do not declare parameter as volatile. " doesn't make sense if `foo` should always generate exactly one read on x. If `foo` is potentially inlininable, the volatile qualifier needs to be there. – Petr Skocik Aug 18 '17 at 12:28
  • @PSkocik: If the object at which `*x` points is declared `int volatile`, then compilers may behave arbitrarily unless `x` is an `int volatile*`, *even if the object at which `x` points is never actually modified by any outside means*. – supercat Sep 29 '17 at 22:50