问题描述
我写了下面一段代码来测试它是否可以通过指针a修改常量c。我已经在 Clang、VC 和 GCC 编译器中对其进行了测试,并观察到 VC 和 GCC 代码都按我的预期工作,它将 6 打印到标准输出,而当我用 Clang 编译它时,该值没有被修改,而 5打印到标准输出。
#include <stdio.h>
int main(void) {
const int c = 5;
int* a = (int*) &c;
*a = 6;
printf("%d",c);
return 0;
}
我想知道对此是否有任何众所周知的解释,或者它与编译器的内部结构和其他难以分析的东西有关。提前谢谢大家!
解决方法
是的。这称为未定义行为。 C11 6.7.3p6:
- 如果尝试通过使用具有非 const 限定类型的左值来修改由 const 限定类型定义的对象,则行为未定义。
具有未定义的行为 explained as:
-
未定义的行为
在使用不可移植或错误的程序结构或错误数据时的行为,本国际标准对此不作任何要求
-
注意:可能的未定义行为范围从完全无视情况并导致不可预测的结果,到翻译或程序执行期间的行为环境特征的记录方式(有或没有诊断消息),以终止翻译或执行(有诊断消息)。
(强调我的)
未启用优化的编译与购买法拉利并始终仅以一档行驶相同。如果您使用 gcc
并使用 -O3
编译会发生什么?
使用 -O3
我的 GCC 生成的代码等同于
#include <stdio.h>
int main(void) {
printf("%d",5);
return 0;
}
那么这个程序怎么样:
#include <stdio.h>
int main(void) {
const char *foo = "Hello world!";
char *bar = (char*)foo;
bar[0] = 'C';
printf("%s\n",foo);
}
使用我的 GCC 和 -O3
它崩溃了。但是如果你使用 Clang,它会打印 Hello world!
... 并且如果你查看程序集 Clang 发现它可以被优化为
int main(void) {
puts("Hello world!");
}
,
此代码的常见可能性包括:
- 编译器看到
const int c = 5;
表示c
的唯一定义值是 5,因此为printf("%d",c);
生成代码,打印“5”而不加载c
的值根据记忆或检查有关c
的任何其他信息。 - 编译器将
c
放在堆栈上。然后int* a = (int*) &c;
获取c
的地址。对于*a = 6;
,编译器生成将 6 写入a
指向的位置的代码,该位置是存储c
的位置。由于堆栈的使用方式,将其或其部分设置为只读是不切实际的,因此处理器不会为这段将 6 写入 C 中定义的位置const
的代码生成任何异常. 然后printf("%d",c);
从内存中获取c
的值,得到 6,并打印“6”。
C 标准当然没有定义这些行为,但这些行为是编译器、硬件和操作系统设计方式的结果。
您可能更有可能看到前一种行为在优化水平较高时而后一种行为在优化关闭时出现,但情况可能会有所不同。