#include <iostream>
using namespace std;
int main() {
const int a = 10;
auto *b = const_cast<int *>(&a);
*b = 20;
cout << a << " " << *b;
cout << endl << &a << " " << b;
}
输出如下:
10 20
0x7ffeeb1d396c 0x7ffeeb1d396c
a
和*b
位于同一地址,为什么它们有不同的价值?
答案 0 :(得分:9)
很可能这是由优化引起的。
正如molbdnilo在评论中已经说过的那样:&#34;编译器盲目地信任你。如果你骗他们,可能会发生奇怪的事情。&#34;
因此,当启用优化时,编译器会找到声明
const int a = 10;
并且认为&#34;啊,这永远不会改变,所以我们不需要真正的&#34;变量,可以简单地用a
&#34;替换代码中10
的所有外观。此行为称为constant folding。
现在在下一行你&#34;作弊&#34;编译器:
auto *b = const_cast<int *>(&a);
*b = 20;
并改变一个,虽然你已经答应不这样做。 结果是混乱。
就像很多人已经提到的那样,Christian Hackl在他出色的深度答案中已经彻底分析了它的未定义行为。通常允许编译器对常量应用常量折叠,常量显式声明为const
。
你的问题是一个非常好的例子(我不知道任何人都可以投票!)为什么const_cast
非常危险(当与原始指针组合时更多),应该只使用如果绝对必要,至少应该至少彻底评论为什么它被使用,如果它是不可避免的。在这种情况下,评论很重要,因为它不仅是你的编译器而且是作弊&#34;:
此外,您的同事将依赖信息const
,并依赖其不会发生变化的信息。如果它改变了,但你没有通知他们,也没有发表评论,也不知道你在做什么,那么你将在工作中度过糟糕的一天:)
尝试一下:也许你的程序在调试版本(未优化)和发布版本(优化)中的行为会有所不同,而且这些错误通常很难解决。
答案 1 :(得分:2)
我认为像这样查看const
会有所帮助:
如果你声明某些东西为const
,那么实际上并不是编译器确保你不改变它,而是相反:你向编译器做出一个你不会改变它的承诺。
这种语言可以帮助你保守你的承诺,例如你做不到:
const int a = 5;
a = 6; // error
但正如您所发现的,您确实可以尝试修改您声明为const
的内容。然而,那么你违背了你的承诺,并且一如既往地在c ++中,一旦你违反了你自己的规则(也就是未定义的行为)。
答案 2 :(得分:2)
a
和*b
位于同一地址,为什么它们有所不同 值?
他们没有,不是根据C ++。根据C ++,他们也没有。
怎么会这样?答案是C ++语言没有定义程序的行为,因为您修改了const
对象。这是不允许的,并且对于在这种情况下会发生什么没有规则。
您只能使用const_cast
来修改首先不是const
的内容:
int a = 123;
int const* ptr1 = &a;
// *ptr1 = 456; // compilation error
int* ptr2 = const_cast<int*>(ptr1);
*ptr2 = 456; // OK
所以你最终得到了一个程序,其行为不是由C ++定义的。相反,行为由编译器编写器,编译器设置和纯粹机会定义。
这也是你的问题出了什么问题。如果不确切地知道你如何编译它以及它运行在哪个系统上,也不可能给你一个正确的答案,甚至可能涉及随机因素。
您可以检查二进制文件以找出编译器认为它正在做什么;甚至还有像https://gcc.godbolt.org/这样的在线工具。但尝试使用printf
代替std::cout
进行此类分析;它将产生更易于阅读的汇编代码。您也可以使用更容易识别的数字,例如123和456.
例如,假设您甚至使用GCC,请比较这两个程序;唯一的区别是const
的{{1}}修饰符:
a
查看#include <stdio.h>
int main() {
int const a = 123;
auto *b = const_cast<int *>(&a);
*b = 456;
printf("%d", a);
printf("%d", *b);
}
#include <stdio.h>
int main() {
int a = 123;
auto *b = const_cast<int *>(&a);
*b = 456;
printf("%d", a);
printf("%d", *b);
}
电话的内容。在printf("%d", a);
版本中,它变为:
const
在非mov esi, 123
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
版本中:
const
如果不太了解汇编代码的细节,可以看到编译器优化了mov eax, DWORD PTR [rbp-12]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
变量,使得值123直接推送到const
。它并没有真正将printf
视为a
调用的变量,但它确实用于指针初始化。
这种混乱是未定义行为的本质。这个故事的寓意是你应该总是避免未定义的行为,在投射之前思考两次是允许编译器帮助你完成这项工作的重要部分。
答案 3 :(得分:0)
使用const
编译代码一次,使用gcc编译一次const
,表示没有为a
分配空间,或者在const
中忽略了内容}版本。 (即使是0优化)它们的汇编结果的唯一区别是打印a
的行。编译器只打印10
而不是引用内存并获取a
的内容:(左侧是const int a
,右侧是int a
版本)
movl $10, %esi | movl -36(%rbp), %eax
> movl %eax, %esi
正如您所看到的,对于非const版本存在额外的内存访问,我们知道这在时间上是昂贵的。因此,编译器决定信任您的代码,并得出结论:永远不会更改const
,并在引用变量的任何地方替换它的值。
编译器选项:--std=c++11 -O0 -S