Home > Back-end >  Is it possible to change value of a constant variable via reinterpret_cast?
Is it possible to change value of a constant variable via reinterpret_cast?

Time:01-06

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.

CodePudding user response:

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.

CodePudding user response:

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...

CodePudding user response:

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)
  •  Tags:  
  • Related