【问题标题】:Why can I change a local const variable through pointer casts but not a global one in C?为什么我可以通过指针强制转换来更改局部 const 变量,而不是 C 中的全局变量?
【发布时间】:2023-04-08 08:55:01
【问题描述】:

我想用指针改变一个常量的值。

考虑下面的代码

int main()
{
    const int const_val = 10;
    int *ptr_to_const = &const_val;

    printf("Value of constant is %d",const_val);
    *ptr_to_const = 20;
    printf("Value of constant is %d",const_val);
    return 0;
}

正如预期的那样,常量的值被修改了。

但是当我使用全局常量尝试相同的代码时,我得到了以下运行时错误。 Windows 崩溃报告器正在打开。在打印此语句“*ptr_to_const = 20;”中的第一个 printf 语句后,可执行文件正在暂停

考虑下面的代码

const int const_val = 10;
int main()
{
    int *ptr_to_const = &const_val;
    printf("Value of constant is %d",const_val);
    *ptr_to_const = 20;
    printf("Value of constant is %d",const_val);
    return 0;
}

这个程序是在mingw环境下用codeblocks IDE编译的。

谁能解释这是怎么回事?

【问题讨论】:

  • 复制:stackoverflow.com/questions/712334/…(下面的答案解释了当它在只读内存而不是读写堆栈中时会发生什么)
  • Windows 崩溃报告器正在打开。打印第一条 printf 语句后,可执行文件正在停止
  • 这是我所期望的。请注意,printf() 消息并不能很好地指示崩溃发生的位置,并且调用未定义行为的程序可以做任何事情(包括您认为它应该具有的行为)。
  • 为什么他可以改变本地 const 的值?
  • 语句 *ptr_to_const = 20; 出现运行时错误;

标签: c pointers constants


【解决方案1】:

它是一个常数,无论如何您都在使用一些技巧来更改它,因此会导致未定义的行为。全局常量可能在只读内存中,因此无法修改。当您尝试这样做时,您会遇到运行时错误。

常量局部变量在栈上创建,可以修改。所以在这种情况下你可以改变常数,但它仍然可能导致奇怪的事情。例如,编译器可以在不同的地方使用常量的值而不是常量本身,因此“更改常量”不会在这些地方显示任何效果。

【讨论】:

    【解决方案2】:

    它在只读存储器中!

    基本上,您的计算机使用两级页表系统将虚拟地址解析为物理地址。伴随着这个宏大的数据结构而来的是一个特殊的位,它表示一个页面是否可读。这很有帮助,因为用户进程可能不应该过度编写自己的程序集(尽管自修改代码有点酷)。当然,他们可能也不应该过度编写自己的常量变量。

    您不能将“const”函数级变量放入只读内存,因为它位于堆栈中,它必须位于可读写页面上。但是,编译器/链接器会看到您的 const,并通过将其放入只读内存(它是常量)来帮您一个忙。显然,覆盖这将导致内核的各种不愉快,内核将通过终止进程来消除对进程的愤怒。

    【讨论】:

    • 一个菜鸟问题,但全局变量的值是可编辑的,对吧?那么,编译器是否看到“const”并分配到只读内存中?(准确吗?这可以通过什么方式独立于机器?)
    【解决方案3】:

    只有在您确定指向的变量最初是非常量(并且您恰好有一个指向它的 const 指针)时,才在 C 和 C++ 中抛弃指针 const 是安全的。否则,它是未定义的,并且取决于您的编译器、月相等,第一个示例也很可能会失败。

    【讨论】:

      【解决方案4】:

      您甚至不应该期望一开始就修改该值。根据标准,这是未定义的行为。全局变量和首先都是错误的。只是不要这样做 :) 它可能会以其他方式崩溃,或者本地和全局崩溃。

      【讨论】:

        【解决方案5】:

        这里有两个错误。第一个是:

        int *ptr_to_const = &const_val;
        

        这是根据 C11 6.5.4/3 的约束违规(早期标准有类似的文本):

        约束

        涉及指针的转换,除了 6.5.16.1 的约束允许的情况外,应通过显式强制转换来指定

        const int *int * 的转换是6.5.16.1 的约束不允许的(可以查看here)。

        令人困惑的是,当一些编译器遇到约束冲突时,他们会写“警告”(甚至根本不写,取决于开关)并假装您在代码中写了其他内容,然后继续。这通常会导致程序的行为不像程序员预期的那样,或者实际上没有以任何可预测的方式运行。为什么编译器会这样做?打败了我,但它肯定会导致诸如此类的问题层出不穷。


        gcc,看起来就像你写了int *ptr_to_const = (int *)&const_val;

        这段代码不违反约束,因为使用了显式转换。然而,这给我们带来了第二个问题。然后*ptr_to_const = 20; 行尝试写入const 对象。这导致undefined behaviour,标准中的相关文本在 6.7.3/6 中:

        如果尝试通过使用具有非 const 限定类型的左值来修改使用 const 限定类型定义的对象,则行为未定义。

        此规则是语义规则,而不是约束规则,这意味着标准不要求编译器发出任何类型的警告或错误消息。该程序是错误的,并且可能以荒谬的方式运行,出现任何奇怪的症状,包括但不限于您观察到的情况。

        【讨论】:

          【解决方案6】:

          由于规范中没有定义这种行为,它是特定于实现的,所以不可移植,所以不是一个好主意。

          为什么要更改常量的值?

          【讨论】:

          • @udpsunil:使用一些可以根据需要定义的宏用于此类测试目的可能是个好主意
          • @Buggieboy 不,它没有定义实现。它只是未定义的。那是两件不同的事情!
          【解决方案7】:

          注意:这是对 Can we change the value of an object defined with const through pointers? 的回答,它作为重复链接到此问题。

          该标准对编译器必须如何处理构造指向const 对象的指针并尝试写入该对象的代码没有任何要求。一些实现——尤其是嵌入式的——可能具有有用的行为(例如,使用非易失性 RAM 的实现可以合法地将 const 变量放置在可写的内存区域中,但即使该单元是断电和备份),并且标准对编译器如何处理创建指向const 内存的非const 指针的代码没有要求这一事实并不影响此类代码在明确允许的实现上的合法性。然而,即使在这样的实现中,替换以下内容可能是个好主意:

          volatile const uint32_t action_count;
          
          BYPASS_WRITE_PROTECT = 0x55; // Hardware latch which enables writing to
          BYPASS_WRITE_PROTECT = 0xAA; // const memory if written with 0x55/0xAA
          BYPASS_WRITE_PROTECT = 0x04; // consecutively followed by the bank number
          *((uint32_t*)&action_count)++;
          BYPASS_WRITE_PROTECT = 0x00; // Re-enable write-protection of const storage
          

          void protected_ram_store_u32(uint32_t volatile const *dest, uint32_t dat)
          {
              BYPASS_WRITE_PROTECT = 0x55; // Hardware latch which enables writing to
              BYPASS_WRITE_PROTECT = 0xAA; // const memory if written with 0x55/0xAA
              BYPASS_WRITE_PROTECT = 0x04; // consecutively followed by the bank number
              *((volatile uint32_t*)dest)=dat;
              BYPASS_WRITE_PROTECT = 0x00; // Re-enable write-protection of const storage
          }
          void protected_ram_finish(void) {}
          ...
          protected_ram_store(&action_count, action_count+1);
          protected_ram_finish();
          

          如果编译器倾向于对写入const 存储的代码应用不需要的“优化”,则将“protected_ram_store”移动到单独编译的模块中可以防止此类优化。它也可能会有所帮助,例如代码需要移动到使用其他协议写入内存的硬件。例如,某些硬件可能会使用更复杂的写入协议来最小化错误写入的可能性。拥有一个明确目的是写入“正常常量”内存的例程将使此类意图变得清晰。

          【讨论】:

            猜你喜欢
            • 2021-06-20
            • 2013-09-13
            • 1970-01-01
            • 2021-11-11
            • 1970-01-01
            • 2016-05-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多