【问题标题】:Is it possible to change value of a constant variable via reinterpret_cast?是否可以通过 reinterpret_cast 更改常量变量的值?
【发布时间】:2022-01-05 13:13:58
【问题描述】:

所有。我从一本书中读到了一段代码 sn-p,作者试图通过直接内存访问来设置寄存器的值(他模拟了这个过程)。为此,他使用了 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。因此,我切换到调试模式,以便在每个步骤中查看发生了什么。我将在下面插入图片:

第 1 步

第 2 步

第 3 步

第 4 步

第 5 步

很抱歉将调试输出包含为图像,但我认为包含图像会更好,所以我不会错过任何重要的细节。对我来说有趣的是,程序输出是 5,而调试器清楚地表明 a 的值是 10? (我什至打印了 reinterpret_cast 之前和之后的地址,它们是一样的。)非常感谢。

【问题讨论】:

  • 您不能更改const 变量的值,句号。尝试这样做会调用未定义的行为,然后任何事情都可能发生。
  • 未定义的行为会做什么,K$n_WCg3dZ"iQ.FR
  • 了解什么是 Undefined Behavior 是学习 C++ 的重要一步。观察具有未定义行为的程序的结果,比如这个,不会教你任何关于语言的知识。
  • 除了未定义的行为。使用const std::uint8_t a = 5;,您说a 是const 并保存值5。由于编译器可以将std::cout &lt;&lt; unsigned(a) &lt;&lt; std::endl; 优化为std::cout &lt;&lt; unsigned(5) &lt;&lt; std::endl;,因为它假定a 永远不会改变。
  • 我有一个问题,有人努力使某些东西成为 const (可能是为了确保一些设计约束)。你为什么要把它扔掉?如果你真的需要一个非常量变量,你可以将数据复制到一个非常量变量中并使用它。

标签: c++ constants reinterpret-cast


【解决方案1】:

我的目的是改变常量变量的值

我们不能也不应该尝试修改const 变量的值。还有,

任何修改 const 对象的尝试都会导致未定义的行为

未定义的行为意味着任何事情1都可能发生包括但不限于给出您预期输出的程序。但是永远不要依赖(或根据)具有未定义行为的程序的输出。

因此,您看到的输出是未定义行为的结果。正如我所说,不要依赖具有 UB 的程序的输出。


1有关未定义行为的更准确的技术定义,请参阅this,其中提到:对程序的行为没有限制 .

【讨论】:

    【解决方案2】:

    从技术上讲,C++ 提供const_cast 来添加或删除变量上的const 修饰符。当 non const 变量临时转换为 const 变量时,它确实具有实际用途,例如,因为现有函数要求它是 const。

    如果您稍后尝试使用一个声明为 const 的变量,并且其值在此期间发生了变化,那么您就是在显式调用未定义行为,这意味着 每个标准 任何事情都可以发生,从预期行为到立即(或延迟)崩溃。

    如果是常见的编译器,通常无法预测您将获得原始值还是更改后的值,因为它实际上取决于优化选项以及编译器如何翻译您的源代码的内部结构。

    如果编译器决定将变量存储在只读内存段中,您可能会收到段错误错误...

    【讨论】:

      【解决方案3】:

      让我感兴趣的是,程序输出是5,而调试器清楚地表明a的值是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 &lt;&lt; unsigned(a) &lt;&lt; std::endl;替换为std::cout &lt;&lt; unsigned(5) &lt;&lt; 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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多