为什么我可以通过指针转换来改变一个局部的constvariables,而不是C中的全局variables呢?

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

考虑下面的代码

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崩溃记者正在开放。 在这个语句中打印第一个printf语句之后,可执行程序正在停止“* ptr_to_const = 20;”

考虑下面的代码

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

该程序是在带有代码块IDE的mingw环境中编译的。

任何人都可以解释发生了什么事?

这是在只读内存!

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

你不能把一个“const”函数级variables放到只读存储器中,因为它存在于堆栈中,它必须在读写页面上。 但是,编译器/链接器会看到你的const,并且把它放在只读存储器中(这是常量),是否有利于你。 显然,覆盖会导致内核的各种不满,他们会通过终止这个进程来消除这个过程中的愤怒。

这是一个常数,你使用一些技巧来改变它,所以不确定的行为结果。 全局常量可能在只读内存中,因此无法修改。 当你尝试这样做,你会得到一个运行时错误。

常量局部variables在堆栈上创build,可以修改。 所以你在这种情况下改变常数就可以避开,但是它可能仍然会导致一些奇怪的事情。 例如,编译器可以在各个地方使用常量的值,而不是常量本身,所以“改变常量”在这些地方没有任何效果。

在C和C ++中抛弃指针常量只有在确定指向的variables最初是非const的(而且恰好有一个指向它的const指针)时才是安全的。 否则,它是不确定的,根据你的编译器,月亮的相位等等,第一个例子可能会失败。

你甚至不应该期望值得修改的地方。 按照这个标准,这是不确定的行为。 全球variables和首先是错误的。 只是不这样做:)它可能已经崩溃了,或者与本地和全球。

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

 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限定types的左值来修改用const限定types定义的对象,则行为是不确定的。

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

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

你为什么要改变一个常量的值?

注意:这是作为一个答案我们可以通过指针来改变一个用const定义的对象的值吗? 其中链接到这个问题作为重复。

该标准并没有要求编译器必须对代码做些什么,这些代码构造了一个指向const对象的指针并试图写入它。 某些实现 – 特别是embedded式实现 – 可能会有一些有用的行为(例如,使用非易失性RAM的实现可以合法地将constvariables放置在可写的内存区域,但即使单元断电以及这个标准并没有规定编译器如何处理创build非常量指针到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”移动到单独编译的模块中可以防止这种优化。 这也可能是有帮助的,例如代码需要转移到使用其他协议写入内存的硬件。 例如,某些硬件可能会使用更复杂的写入协议来最大限度地减less错误写入的可能性。 有一个例程,其明确的目的是写入“正常”的内存将使这样的意图明确。