繁体   English   中英

为什么我可以通过指针转换而不是C中的全局变量来更改本地const变量?

[英]Why can I change a local const variable through pointer casts but not a global one in C?

我想通过使用指针来改变常量的值。

请考虑以下代码

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;
}

该程序使用codeblocks IDE在mingw环境中编译。

谁能解释一下发生了什么?

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

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

它在只读内存中!

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

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

如果你确定指向变量最初是非const的(并且你碰巧有一个指向它的const指针),那么在C和C ++中抛弃指针常量是唯一安全的。 否则,它是未定义的,并且取决于您的编译器,月亮的阶段等,第一个示例也可能很好地失败。

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

int *ptr_to_const = &const_val;

根据C11 6.5.4 / 3(早期标准有相似的文字),这是一种违反约束的行为

约束

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

从转换const int *int *并不受6.5.16.1约束(其可被视为允许在这里 )。

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


gcc,似乎就像你写了int *ptr_to_const = (int *)&const_val;

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

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

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

您甚至不应期望在第一时间修改该值。 根据标准,它是未定义的行为。 全局变量和首先是错误的。 只是不要这样做:)它可能已经崩溃了,或与本地和全球崩溃。

注意:这是作为一个答案我们可以通过指针更改用const定义的对象的值吗? 将此问题作为副本链接。

标准对编译器必须对构造指向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”移动到单独编译的模块中可以防止这种优化。 它也可能有用,例如代码需要移动到使用其他协议写入内存的硬件。 例如,某些硬件可能使用更复杂的写入协议来最小化错误写入的可能性。 具有明确目的是写入“正常常数”存储器的例程将使这样的意图清楚。

由于此行为未在规范中定义,因此它是特定于实现的,因此不可移植,因此不是一个好主意。

为什么要更改常量的值?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM