views:

190

answers:

2
int x = 2;
volatile int y = 2;

const int z = x/y;

int main(){
    int x = 2 + 3;

    double d = 7 / 3;
}

I have three questions here:

Firstly, can the compiler calculate the value of the 'z' at compile time to be 1 in this case?

Secondly, I observed that the compiler does not generate assembly instructions for adding 2 and 3 to initialize x. It directly initializes x with 5. Can the same be done with 'd'?

Thirdly, Is there any good book to read on these two concepts? Any quotes from the Standard would be helpful (The standard document seems to be an interesting place though very scary)

+4  A: 

Firstly, can the compiler calculate the value of the 'z' at compile time to be 1 in this case?

No. reading or writing to volatile variables considered having side effects, so the compiler is not allowed to do this.

Secondly, I observed that the compiler does not generate assembly instructions for adding 2 and 3 to initialize x. It directly initializes x with 5. Can the same be done with 'd'?

Yes. As long as the compiler can prove that there are no side effect. E.g. if overflow or a divide by zero happens during the computation, it can't compute it at compile time since the computation should trigger a CPU exception at runtime.

Thirdly, Is there any good book to read on these two concepts?

Yes. C++ ISO standard describes exactly what you're asking. Books are good to learn the basics or the theory. It makes no sense writing books that rephrase all the technical details described in the standard.

ybungalobill
Anyway, the standard describes only the results you'll have, and not the optimizations that could or should the compiler do.
ruslik
Thanks. My doubt is exactly this. If the compiler can statically compute the value of 7/3, then it represents the floating point result in which format? Is it mandatorily IEEE754?
Nivhus
@Nivhus I think it depends on the platform. It's only garanteed that on this platform this value would be interpreted as 7/3.
ruslik
+1, good answer but the last paragraph doesn't sound accurate. Code optimization is an implementation detail.
Hans Passant
@Johannes: "compiler could initialize z to 1 statically" No. It can't know that reading y at runtime will yield 2. So it can't do this. And when it comes to volatile memory the standard speaks about side effects, quote: "Accessing an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/Ofunction, or calling a function that does any of those operations are all side effects, which are changes in thestate of the execution environment." and also "Access to volatile objects are evaluated strictly according to the rules of the abstract machine."
ybungalobill
@ybungalobill um please disregard anything I wrote. I see now that saying "reading volatile variables is considered having side effects" is enough to prevent the compiler to optimize it, even if side effects alone are not observable behavior. And you are also right of course about "z". If we won't read "y" we will miss on an observable behavior. I need some coffee now, cheers!
Johannes Schaub - litb
+1  A: 

As for "first" - y must be accessed, when z is initialized, but I don't think the result of that access has to be used to calculate z, if the implementation somehow knows that it must be 2. For this program there are (I think) only 2 ways it can have any other value:

  1. it's modified by a debugger or other interference with the program.
  2. the loader places volatile globals in some region of memory that doesn't behave like normal memory on your hardware. (in this case that would be very odd implementation-defined behavior indeed, to the point I don't think it's legal for the code as written, but it's relevant if the program, or some part of the build process outside the program, can somehow control where the object ends up).

Both of those are things that the implementation can rule out - in the second case by knowing how the loader behaves, in the first place by placing limitations on what you can hope to achieve with a debugger ("writing volatile variables results in surprising behavior"). Disappointing for the user of the debugger, but the standard doesn't constrain how debuggers work, or what memory "actually" contains, it just constrains what valid C++ implementations and programs do, and what C++ "sees".

In practice you'd think the compiler wouldn't bother treating a volatile object as subject to optimisations. It's a non-const object, and you've got to suspect that the only reason for defining a non-const volatile object is because it's going to change in ways the compiler doesn't expect[*]. You'd hope it will just read y and do the division, but I reckon a case could be made for optimisation being legal.

As for "second" - in the case of your program, the compiler can initialize d with a pre-computed value under the "as-if" rule, provided it knows what value division produces. For that matter in your program it can remove d entirely.

"Provided it knows what value division produces" depends on the implementation - if it supports changes to IEEE rounding modes or equivalent, and if it doesn't know what mode should be in force, then in general it doesn't know in advance the result of even simple arithmetic.

The "as-if" rule covers, say, 85% of compiler optimisations. It's covered in section 1.9 of the standard, which is worth a look. I agree that the document as a whole is pretty intimidating, and the language it uses is sometimes impenetrable, but you have to start somewhere, so start with whatever you're currently interested in ;-)

[*] Specifically, and this is not something that's addressed in the C++03 standard in any way, some compilers (Microsoft) involve volatile in their definition of threading semantics.

Steve Jessop
Thanks. So I understand that unless the compiler can prove (somehow) that y is not 2 at library initialization time, the compiler will definitely read y to initialize z. Even if it does not, it is still a conforming behavior. Is that correct?
Nivhus
In my example, assuming 'd' is not completely removed, my doubt still remains if the value of 7/3 will be computed in IEEE754 standard? What if the target architecture does not support IEEE754? I think this issue does not hold for simple arithmetic for example (2+3) which would be universal on all platforms.
Nivhus
It will certainly read y if it can't prove that it *is* 2. What I'm unsure about, is exactly what circumstances it would be able to prove that it is 2. If it wants to support the value being changed by a debugger at a very early point of program initialization, then of course it can't prove it. If it wants to support `y` being located at a magic address that reads some input port, it can't prove it. But I reckon you could legally write a C++ implementation in which `volatile` does nothing, despite 1.9/6, if you know that actually, reads/writes to memory aren't "observable" *on this hardware*.
Steve Jessop
@Nivhus: the compiler might know whether the target architecture uses IEEE floats, or it might not. Some implementations are closely tied to particular hardware (or can be so tied with the right command-line options). So they might assume IEEE, or there might be a float emulation library built into the compiler, and it can assume the results will match those of the library. Other implementations support many different types of hardware, and would not want to make any assumptions.
Steve Jessop
Actually on second thoughts, I have a feeling the implementation is allowed to precompute constant expressions involving floating point arithmetic *even if* the result doesn't match the same computation performed at runtime. That wouldn't be under the "as-if" rule, it would have to be explicitly permitted. If I'm right, and if I can find a reference in the standard, I'll update.
Steve Jessop