C ++如何保持const值?

时间:2017-11-11 10:00:54

标签: c++ compiler-optimization

#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位于同一地址,为什么它们有不同的价值?

4 个答案:

答案 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