3
#include <iostream>
using namespace std;
int main() {
    const int a = 10;
    auto *b = const_cast<int *>(&a);
    *b = 20;
    cout << a << " " << *b;
    cout << endl << &a << " " << b;
}

The output looks like:

10 20
0x7ffeeb1d396c 0x7ffeeb1d396c

The a and *b are at the same address, why do they have different value?

sorush-r
  • 10,490
  • 17
  • 89
  • 173
Joshua Shi
  • 343
  • 1
  • 6

4 Answers4

9

Most probably this is caused by optimization.

As molbdnilo already said in his comment: "Compilers trust you blindly. If you lie to them, strange things can happen."

So when optimization is enabled, the compiler finds the declaration

const int a = 10;

and thinks "ah, this will never change, so we don't need a "real" variable and can replace all appearances of a in the code simply with 10". This behavior is called constant folding.

Now in the next line you "cheat" the compiler:

auto *b = const_cast<int *>(&a);
*b = 20;

and change a, although you have promised not to do so. The result is confusion.

Like a lot of people have already mentioned and Christian Hackl has thoroughly analyzed in his excellent in-depth answer, it's undefined behavior. The compiler is generally allowed to apply constant folding on constants, which are explicitly declared const.

Your question is a very good example (I don't know how anyone can vote it down!) why const_cast is very dangerous (and even more when combined with raw pointers), should be only used if absolutely necessary, and should be at least thoroughly commented why it was used if it was unavoidable. Comments are important in that case because it's not only the compiler you are "cheating":

Also your co-workers will rely on the information const and rely on the information that it won't change. If it DOES change though, and you didn't inform them, and did not comment, and did not exactly know what you were doing, you'll have a bad day at work :)

Try it out: Perhaps your program will even behave different in debug build (not optimized) and release build (optimized), and those bugs are usually pretty annoying to fix.

user2328447
  • 1,807
  • 1
  • 21
  • 27
2

I think it helps to view const like this:

If you declare something as const it is actually not the compiler that ensures that you dont change it, but rather the opposite: you make a promise to the compiler that you wont change it.

The language helps you a bit to keep your promise, eg you cannot do:

const int a = 5;
a = 6;            // error

but as you discovered, you indeed can attempt to modify something that you declared const. However, then you broke your promise and as always in c++, once you break the rules you are on your own (aka undefined behaviour).

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
2

The a and *b are at the same address, why do they have different value?

They don't, not according to C++. Neither do they, according to C++.

How can that be? The answer is that the C++ language does not define the behaviour of your program because you modify a const object. That's not allowed, and there are no rules for what will happen in such a case.

You are only allowed to use const_cast in order to modify something which wasn't const in the first place:

int a = 123;
int const* ptr1 = &a;
// *ptr1 = 456; // compilation error
int* ptr2 = const_cast<int*>(ptr1);
*ptr2 = 456; // OK

So you've ended up with a program whose behaviour is not defined by C++. The behaviour is instead defined by compiler writers, compiler settings and pure chance.

That's also what's wrong with your question. It's impossible to give you a correct answer without knowing exactly how you compiled this and on which system it runs, and perhaps even then there are random factors involved.

You can inspect your binary to find out what the compiler thought it was doing; there are even online tools like https://gcc.godbolt.org/ for that. But try to use printf instead of std::cout for such an analysis; it will produce easier-to-read assembly code. You may also use easier-to-spot numbers, like 123 and 456.

For example, assuming that you even use GCC, compare these two programs; the only difference is the const modifier for a:

#include <stdio.h>
int main() {
    int const a = 123;
    auto *b = const_cast<int *>(&a);
    *b = 456;
    printf("%d", a);
    printf("%d", *b);
}

#include <stdio.h>
int main() {
    int a = 123;
    auto *b = const_cast<int *>(&a);
    *b = 456;
    printf("%d", a);
    printf("%d", *b);
}

Look at what the printf("%d", a); call becomes. In the const version, it becomes:

mov esi, 123
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf

In the non-const version:

mov eax, DWORD PTR [rbp-12]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf

Without going too much into the details of assembly code, one can see that the compiler optimised the const variable such that the value 123 is pushed directly to printf. It doesn't really treat a as a variable for the printf call, but it does do so for the pointer initialisation.

Such chaos is the nature of undefined behaviour. The moral of the story is that you should always avoid undefined behaviour, and thinking twice before casting is an important part of allowing the compiler to help you in that endeavor.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
0

Compiling your code once with const and once without const using gcc, shows that there either no space is allocated for a, or it's content is being ignored in const version. (even with 0 optimization) The only difference in their assembly result is the line which you print the a. Compiler simply prints 10 instead of refering to memory and fetching contents of a: (left side is const int a and right side is int a version)

movl    $10, %esi     |     movl    -36(%rbp), %eax
                      >     movl    %eax, %esi

As you can see there is an extra memory access for non-const version, which we know is expensive in terms of time. So the compiler decided to trust your code, and concluded that a const is never to be changed, and replaced its value wherever the variable is referenced.

Compiler options: --std=c++11 -O0 -S

sorush-r
  • 10,490
  • 17
  • 89
  • 173