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