Reputation: 9203
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.
Upvotes: 3
Views: 309
Reputation: 60107
int foo(volatile int * x) { return *x + *x; }
has to generate two reads. Volatile accesses can't be optimized out. They're like IO.
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.)
Upvotes: 2
Reputation: 12404
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.
Upvotes: 3
Reputation: 107819
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.
Upvotes: 4