-1

I was reading about casts in c++ and got confused about how are the expressions actually evaluated. Consider the following code in which var is of type int and after the expression var = (var*10)/10 the memory of var gets overflowed:

#include<iostream.h>

void main()
{
  int var = 25000; // signerd int varies from -32768 to 32767
  var = (var*10)/10; // seems like (var*10) is stored in the memory allocated `var`  
  cout << "Incorrect value of var is:" << var << endl;

  var = (long(var)*10)/10;
  cout << "correct value of var is:" << var << endl;
}

The incorrect value shown is -1214 and the correct is, as expected, 25000. I am very much confused about how is the expressions var = (var*10)/10; actually evaluated. Does the compiler evaluates the expression step by step such that the result of first operation[ (var*10) ] performed on the numeric value of var is stored in it's allocated memory then the value stored in that memory space is used for further operations to be performed.

My knee jerk was that the compiler directly computes the value (var*10) then divides this value with 10 but this doesn't seem to be the case.

So the question is,

  • How are expressions, in general and for my specific example, evaluated in C++?

P.S: I know that I am using an outdated dialect of C++ language and C++ compiler.

  • 4
    Badly, I'd imagine. – Lightness Races in Orbit Feb 18 '16 at 17:48
  • 3
    "signerd int varies from -32768 to 32768" Good reason not to use Turbo C++. Outside of the embedded work (e.g. Arduino) this hasn't been true in the mainstream for over twenty years. You're learning dark witchcraft! I'll gloss over the <iostream.h> and void main on this occasion, as you've admitted to complicity already ;) – Lightness Races in Orbit Feb 18 '16 at 17:49
  • @PreferenceBean I know that. I think in modern c++ int is given 4bytes but the overflow will still occur. – user106313 Feb 18 '16 at 17:52
  • More like on modern computers int is usually four bytes now (though not always — the C++ language doesn't fully mandate it so it's up to your platform and toolchain). – Lightness Races in Orbit Feb 18 '16 at 17:53
  • 2
    @PreferenceBean is correct about dark witchcraft. Take whatever the Standard says should happen and throw it out: you are dabbling in an ancient dialect of C++ that predates any formal standard and has many quirks. If you were using a modern compiler we could maybe tell you how it should work, but all we can do with your code is throw darts (guesses) at it. – user22815 Feb 18 '16 at 17:53
1

Yes and no. At least in a typical case, the compiler won't store the intermediate results back in the original memory location. It will, however, typically carry out the operation(s) on a register that's the same size, and each intermediate result will be written back to that register. So, for your case, the compiler might generate code something like this:

mov ax, var ; load the value from `var` into register ax
mov bx, 10  ; load 10 into register dx
imul bx     ;  multiply ax * bx. 32-bit result:
            ; 16 least significant bits in AX
            ; 16 most significant bits in DX
xor dx, dx  ; zero dx, in this case losing some significant digits
idiv bx     ; divide ax by bx. 32-bit result:
            ; 16-bit dividend in ax
            ; 16-bit remainder in dx
            ; if dividend won't fit in 16 bits, trigger a "divide by 0" exception
mov var, ax ; store final result back into var's location in memory

Each of these registers (ax, bx, dx, etc.) is 16 bits in size.

When you do the division on a long instead of an int, it does roughly the same thing, but the intermediate will be carried out to 32-bit precision instead of just 16 bit precision.

With a compiler that knew how to generate 32-bit instructions it would come out virtually identical, except that it would replace ax with eax, bx with ebx, and dx with edx. A 16-bit compiler won't know how to generate those instructions though, so it'll normally have to use some library routines to do get 32-bit precision.

3

How are expressions, in general and for my specific example, evaluated in C++?

Expression evaluation is a bit complex to describe in detail in an answer, so I will focus on your examples.


var = (var*10)/10;

In this case, two things may happen. Since the value can be computed at compile-time, the compiler is permitted to perform the calculation the same way it would occur at runtime and store the result directly. Keep in mind that C++ does not specify how anything must occur, it focuses on the results. In other words, the standard basically says "an expression must evaluate to a result using certain semantics," but a compiler is free to precompute, reorder operations, etc. as long as the result is the same.

The other option is the compiler does nothing and the computation occurs at runtime, and this value will overflow and produce undesired results as others have noted.


var = (long(var)*10)/10;

This works the same way, except the intermediate values are stored in a long instead of an int which is generally larger (but not required to be).


Please note that in both examples any computation at runtime will use intermediate values: only the = operator will store the value in var. Any other values may be stored in temporary variables created by the compiler, registers, or in the brains of nasal demons - the standard lets the compiler do whatever it wants as long as the end result is the same.

Further Reading

I recommend reading up on constexpr. While your ancient compiler does not support it, any discussion of this new keyword will go into more depth on the topics I brought up in this answer.

  • Nice. (Yes, that's a cast, using functional cast notation.) – Lightness Races in Orbit Feb 18 '16 at 18:03
  • @PreferenceBean I guess I haven't seen that type of cast before. Ever since the new C++ style casts were introduced I have used those, and of course the (type) variable seems to be much more common. – user22815 Feb 18 '16 at 18:04
  • You see it every time you explicitly construct a temporary. Though arguably only when passing a single constructor argument would one call it an "cast" in any honest sense. Still, same mechanism. It's indeed fairly rare for primitives but I do see it from time to time. I find uint64_t(-1) a lot nicer than (uint64_t)-1, especially if I've got another set of parens around it for whatever reason. – Lightness Races in Orbit Feb 18 '16 at 18:06
2

Does the compiler evaluates the expression step by step such that the result of first operation[ (var*10) ] performed on the numeric value of var is stored in it's allocated memory then the value stored in that memory space is used for further operations to be performed.

Yes.

My knee jerk was that the compiler directly computes the value (var*10) then divides this value with 10 but this doesn't seem to be the case.

I don't see the distinction.

Whether computed at compile-time (thanks, optimiser!) or at run-time, the semantics of var's type still must be adhered to. If it can't hold values higher than 32768, then it can't hold values higher than 32768, and no matter how much magic the compiler performs to run the calculation as quickly as possible, that fact can't and won't change.

Mind you, signed integer overflow is undefined (meaning anything can happen) so I suppose in theory you could see your expected result here, but I wouldn't hold my breath. ;)

  • THe distinction is that the compiler wouldn't store (var*10) in var's memory but somewhere else and take this value(located somewhere else) and divide it with 10. So the var shouldn't have been overflowed. – user106313 Feb 18 '16 at 17:54
  • @user106313: Doesn't matter where the compiler stores it. Your compiler could post calculation instructions to a reclusive genius living atop a mountain in Tibet and wait for a response, but it'd still be an object of type int, and it'd still overflow. The rules of int must always be applied to calculations on that object. The domain of int is specified and required to be adhered to — it's not just an "as it happens" consequence of physical memory storage that can be ignored as irrelevant at convenience. – Lightness Races in Orbit Feb 18 '16 at 17:54
  • How about evaluation of expressions in general. E.g. var = 1 + var - 2. Would the compiler first store the value of var + 1 in var and then subtract 2 from the new value of var? – user106313 Feb 18 '16 at 17:56
  • @user106313: I feel like you're not really listening to me. It doesn't matter where the compiler stores it. You do not need to care where the compiler stores it. Concentrate on the specified semantics of the operation, and you'll come out on top. Besides, now you're asking a completely different question, which is about operator precedence. – Lightness Races in Orbit Feb 18 '16 at 17:57
  • 1
    @user106313: Yep. Every single value and object in your program has a type; doesn't matter whether it's assigned to a variable or just kept as a literal in a calculation. – Lightness Races in Orbit Feb 19 '16 at 3:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.