0

all. I have read a code snippet from a book where the author tries to set the value of a register via direct memory access (he simulates this process). He used reinterpret_cast<volatile uint8_t*> for this. So, after reading his code, out of curiosity I have tried to apply the same code for a constant variable, and I experienced a very interesting output. I inserted the code below which is very simple:

int main()
{
  const std::uint8_t a = 5;
  
  const std::uintptr_t address = reinterpret_cast<std::uintptr_t>(&a);
  
  *reinterpret_cast<volatile uint8_t*>(address) = 10;
  
  std::cout << unsigned(a) << std::endl;

  return 0;
}

So, my purpose is to change the value of constant variable via direct memory access. I have written this code in Visual Studio C++ 2019 and compiled and run it. There was no any error or warning, but the output was very interesting. The value of a is printed as 5. So, I switched to the debug mode in order to see at each step what is happening. I will insert the images below:

Step 1 enter image description here

Step 2 enter image description here

Step 3 enter image description here

Step 4 enter image description here

Step 5 enter image description here

I am sorry to include debugging output as images, but I thought that it would be better to include images, so I will not miss any important detail. The thing that is interesting for me, how the program output is 5, while debugger clearly indicates the value of a is 10? (I even printed the addresses of a before and after the reinterpret_cast and they were the same.) Thank you very much.

BHOS
  • 91
  • 7
  • 13
    You cannot change the value of a `const` variable, full stop. Trying to do so invokes undefined behavior and anything can happen then. – NathanOliver Jan 05 '22 at 13:14
  • 2
    Undefined behaviour will do whatev,K$n_WCg3dZ"iQ.FR – Quimby Jan 05 '22 at 13:17
  • 1
    Learning about what counts as [Undefined Behavior](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior) is an important step in learning C++. Observing the results of a program with Undefined Behavior, like this one, will not teach you anything about the language. – Drew Dormann Jan 05 '22 at 13:23
  • 2
    Besides undefined behavior. With `const std::uint8_t a = 5;` you say that `a` is const and holds the value `5`. Due to that a compiler can optimize `std::cout << unsigned(a) << std::endl;` to `std::cout << unsigned(5) << std::endl;` because it assumes `a` will never change. – t.niese Jan 05 '22 at 13:23
  • I have a question back, someone went through the effort to make something const (probably to ensure some design constraints). Why would you want to cast that away? If you really need a non-const variable you can copy the data into a non-const variable and use that. – Pepijn Kramer Jan 05 '22 at 13:24
  • To be more clear about what might actually happen if you change a constant: the compiler may see a constant and then substitute accesses to the variable with its value as an optimization step. Modifying the `const` variable may result in an incorrect program because the old value is often still used. – AndyG Jan 05 '22 at 13:25
  • Whenever you have the need to use`reinterpret_cast` you should think twice about what you do. There are only rare cases where you want and are allowed to use it. If you want to cast the `const` away you should use `const_cast` and also there you are only allowed to do that in very constraint situations [How to use const_cast?](https://stackoverflow.com/questions/19554841) – t.niese Jan 05 '22 at 13:32
  • *my purpose is to change the value of constant variable* On two occasions I have been asked, "Pray, Mr. Babbage, if you put into the machine wrong figures, will the right answers come out?" ... I am not able rightly to apprehend the kind of confusion of ideas that could provoke such a question. — Charles Babbage, Passages from the Life of a Philosopher – Eljay Jan 05 '22 at 14:08

3 Answers3

3

my purpose is to change the value of constant variable

We can't and should never try to modify the value of a const variable. Also,

Any attempt to modify a const object results in undefined behavior.

Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.

So the output that you're seeing is a result of undefined behavior. And as i said don't rely on the output of a program that has UB.


1For a more technically accurate definition of undefined behavior see this where it is mentioned that: there are no restrictions on the behavior of the program.

Jason
  • 36,170
  • 5
  • 26
  • 60
2

Technically, C++ provides const_cast to add or remove the const modifier on a variable. And it indeed has real world uses when a non const variable was temporarily converted to a const one for example because an existing function requires it to be const.

Simply if you later try to use a variable that was declared to be const and if its value has changed in the meantime, you are explicitely invoking Undefined Behaviour meaning that per standard anything is allowed to happen, from expected behaviour to immediate (or defered) crash.

If common compilers, it is generaly just unpredictable whether you will get the original or the changed value, because it actually depend on the optimization options and of the internals of how the compilers translated your source.

Or you can get a segfault error if the compiler decided to store the variable in a read only memory segment...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
1

The thing that is interesting for me, how the program output is 5, while debugger clearly indicates the value of a is 10?

This depends entirely on the compiler. It could output 10, 5, crash, ... because it is undefined behavior.

If you want to know why the output of the binary created by a particular compiler has a certain result for undefined behavior, you have to look at the generated output of the compiler. This can be done using e.g. godbolt.org

For your code the output gcc (11.2) generates is:

        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     BYTE PTR [rbp-9], 5
        lea     rax, [rbp-9]
        mov     QWORD PTR [rbp-8], rax
        mov     rax, QWORD PTR [rbp-8]
        mov     BYTE PTR [rax], 10
        mov     esi, 5
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)
        mov     esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
        mov     rdi, rax
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
        mov     eax, 0
        leave
        ret

Here you can see that the compiler correctly assumes that the value of a will not change. And replaces std::cout << unsigned(a) << std::endl; with std::cout << unsigned(5) << std::endl;:

        mov     esi, 5
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)

If you remove the const from a the output is:

        movzx   eax, BYTE PTR [rbp-9]
        movzx   eax, al
        mov     esi, eax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(unsigned int)
t.niese
  • 39,256
  • 9
  • 74
  • 101