是否可以通过 reinterpret_cast 更改常量变量的值?
Is it possible to change value of a constant variable via reinterpret_cast?
全部。我从一本书中读到一段代码片段,作者试图通过直接内存访问来设置寄存器的值(他模拟了这个过程)。为此,他使用了 reinterpret_cast。因此,在阅读了他的代码后,出于好奇,我尝试将相同的代码应用于一个常量变量,并且我体验到了一个非常有趣的输出。我在下面插入了非常简单的代码:
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;
}
所以,我的目的是通过直接内存访问来改变常量变量的值。我在 Visual Studio C++ 2019 中编写了这段代码,并对其进行了编译和 运行。没有任何错误或警告,但输出非常有趣。 a 的值打印为 5。因此,我切换到调试模式,以便在每个步骤中查看发生了什么。我将在下面插入图片:
第一步
第 2 步
步骤 3
步骤 4
第5步
很抱歉将调试输出包含为图像,但我认为包含图像会更好,这样我就不会错过任何重要的细节。对我来说有趣的是,程序输出是 5,而调试器清楚地指示 a 的值为 10? (我还打印了reinterpret_cast前后a的地址,都是一样的。)非常感谢
my purpose is to change the value of constant variable
我们不能也不应该尝试修改 const
变量的值。另外,
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.
所以您看到的输出是未定义行为的结果。正如我所说,不要依赖具有 UB 的程序的输出。
1有关未定义行为的技术上更准确的定义,请参阅 this 其中提到:没有对程序行为的限制.
从技术上讲,C++ 提供 const_cast
来添加或删除变量上的 const
修饰符。当 non const 变量临时转换为 const 变量时,它确实有实际用途,例如因为现有函数要求它是 const.
简单地说,如果您稍后尝试使用声明为 const 的变量并且在此期间它的值发生了变化,那么您就是在明确地调用未定义的行为,这意味着 每个标准 任何事情都允许发生,从预期行为到立即(或延迟)崩溃。
如果是普通编译器,通常无法预测您将获得原始值还是更改后的值,因为它实际上取决于优化选项以及编译器如何翻译您的源代码的内部机制。
或者,如果编译器决定将变量存储在只读内存段中,您可能会遇到段错误...
The thing that is interesting for me, how the program output is 5, while debugger clearly indicates the value of a is 10?
这完全取决于编译器。它可以输出 10、5、crash,...,因为它是未定义的行为。
如果你想知道为什么特定编译器创建的二进制文件的输出对未定义行为有一定的结果,你必须查看编译器生成的输出。这可以使用例如完成godbolt.org
对于您的代码,gcc (11.2) 生成的输出是:
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
在这里你可以看到编译器正确地假设a
的值不会改变。并将 std::cout << unsigned(a) << std::endl;
替换为 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)
如果从 a
中删除 const
,则输出为:
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)
全部。我从一本书中读到一段代码片段,作者试图通过直接内存访问来设置寄存器的值(他模拟了这个过程)。为此,他使用了 reinterpret_cast
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;
}
所以,我的目的是通过直接内存访问来改变常量变量的值。我在 Visual Studio C++ 2019 中编写了这段代码,并对其进行了编译和 运行。没有任何错误或警告,但输出非常有趣。 a 的值打印为 5。因此,我切换到调试模式,以便在每个步骤中查看发生了什么。我将在下面插入图片:
第一步
第 2 步
步骤 3
步骤 4
第5步
很抱歉将调试输出包含为图像,但我认为包含图像会更好,这样我就不会错过任何重要的细节。对我来说有趣的是,程序输出是 5,而调试器清楚地指示 a 的值为 10? (我还打印了reinterpret_cast前后a的地址,都是一样的。)非常感谢
my purpose is to change the value of constant variable
我们不能也不应该尝试修改 const
变量的值。另外,
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.
所以您看到的输出是未定义行为的结果。正如我所说,不要依赖具有 UB 的程序的输出。
1有关未定义行为的技术上更准确的定义,请参阅 this 其中提到:没有对程序行为的限制.
从技术上讲,C++ 提供 const_cast
来添加或删除变量上的 const
修饰符。当 non const 变量临时转换为 const 变量时,它确实有实际用途,例如因为现有函数要求它是 const.
简单地说,如果您稍后尝试使用声明为 const 的变量并且在此期间它的值发生了变化,那么您就是在明确地调用未定义的行为,这意味着 每个标准 任何事情都允许发生,从预期行为到立即(或延迟)崩溃。
如果是普通编译器,通常无法预测您将获得原始值还是更改后的值,因为它实际上取决于优化选项以及编译器如何翻译您的源代码的内部机制。
或者,如果编译器决定将变量存储在只读内存段中,您可能会遇到段错误...
The thing that is interesting for me, how the program output is 5, while debugger clearly indicates the value of a is 10?
这完全取决于编译器。它可以输出 10、5、crash,...,因为它是未定义的行为。
如果你想知道为什么特定编译器创建的二进制文件的输出对未定义行为有一定的结果,你必须查看编译器生成的输出。这可以使用例如完成godbolt.org
对于您的代码,gcc (11.2) 生成的输出是:
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
在这里你可以看到编译器正确地假设a
的值不会改变。并将 std::cout << unsigned(a) << std::endl;
替换为 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)
如果从 a
中删除 const
,则输出为:
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)