常见谎言? (因为const可以被抛弃)

时间:2012-06-05 23:57:05

标签: c++ c const

  

可能重复:
  Sell me on const correctness

关键字constCC++中的用处是什么,因为它允许这样的事情?

void const_is_a_lie(const int* n)
{ 
    *((int*) n) = 0;
}

int main()
{
    int n = 1;
    const_is_a_lie(&n);
    printf("%d", n);
    return 0;
}
  

输出:0

很明显const无法保证论证的不可修改性。

5 个答案:

答案 0 :(得分:44)

const是对编译器的承诺,而不是它保证的。

例如,

void const_is_a_lie(const int* n)
{ 
    *((int*) n) = 0;
}

#include <stdio.h>
int main()
{
    const int n = 1;
    const_is_a_lie(&n);
    printf("%d", n);
    return 0;
}

http://ideone.com/Ejogb显示的输出

  

1

由于const,编译器可以假设该值不会改变,因此它可以跳过重读,如果这样可以使程序更快。

在这种情况下,由于const_is_a_lie()违反了合同,因此会发生奇怪的事情。不要违反合同。并且很高兴编译器能帮助您保持合同。演员是邪恶的。

答案 1 :(得分:10)

在这种情况下,n是指向常量int的指针。当您将其强制转换为int*时,您会删除const限定符,因此允许该操作。

如果你告诉编译器删除const限定符,它会很乐意这样做。如果你让它完成它的工作,编译器将帮助确保你的代码是正确的。通过强制转换常量,您告诉编译器您知道n的目标是非常量的,并且您确实想要更改它。

如果您的指针所指向的东西实际上首先被声明为const,那么您通过尝试更改它来调用未定义的行为,并且任何东西都可以发生。它可能会奏效。写操作可能不可见。该计划可能会崩溃。你的显示器可以打你。 (好吧,可能不是最后一个。)

void const_is_a_lie(const char * c) {
    *((char *)c) = '5';
}

int main() {
    const char * text = "12345";
    const_is_a_lie(text);
    printf("%s\n", text);

    return 0;
}

根据您的特定环境,const_is_a_lie中可能存在段错误(又称访问冲突),因为编译器/运行时可能会将字符串文字值存储在不可写的内存页中。

标准有关于修改const对象的说法。

  

7.1.6.1/4 cv-qualifiers [dcl.type.cv]

     

除了可以修改声明为mutable(7.1.1)的任何类成员之外,任何在其生命周期内修改const对象的尝试(3.8)都会导致未定义的行为

“医生,我这样做会很疼!” “所以不要这样做。”

答案 2 :(得分:6)

您...

int n = 1;

...确保读/写内存中存在n;它是一个非const变量,因此稍后修改它的尝试将定义行为。给定这样一个变量,你可以混合使用const和/或非const指针和对它的引用 - 每个指针的常量只是程序员防止意外改变的一种方式。代码的“分支”。我说“分支”是因为您可以将给n的访问可视化为一棵树 - 一旦分支被标记为const,所有子分支(进一步指向/引用{{1是否需要保留n的其他局部变量,函数参数等等,除非你明确地将constness的概念抛弃了。抛弃const对于像const这样可变的变量是安全的(如果可能令人困惑),因为它们最终仍然会写回一个可修改/可变/非的内存地址 - {{1 }}。您可以想象在这些情况下可能会遇到麻烦的所有奇怪的优化和缓存都不允许,因为标准要求并保证在我刚刚描述的情况下的理智行为。

可悲的是,也可以抛弃真正固有的n变量的常量,比如说const,并且任何修改它们的尝试都将具有未定义的行为。这有很多实际的原因,包括编译器将它们放在内存中然后标记为只读的权利(例如参见UNIX const),这样尝试写入将导致CPU陷阱/中断,或从变量读取每当需要原始设置值时(即使代码中从未使用该值提及变量的标识符),或使用原始值的内联编译时副本 - 忽略对变量本身的任何运行时更改。因此,标准使行为未定义。即使它们碰巧按照您的意图进行了修改,程序的其余部分也会有不确定的行为。

但是,这不应该是令人惊讶的。类型的情况也是如此 - 如果你有......

const int o = 1;

...你有没有向编译器说谎mprotect(2)的类型?最终double d = 1; *(int*)&d = my_int; d += 1; 占用的内存可能在硬件级别上是无类型的,因此编译器所拥有的只是对它的一种观点,进而改变位模式。但是,根据d的值和硬件上的双重表示,您可能在d中创建了无效的位组合,这些位组合不代表任何有效的双精度值,以便随后尝试将内存读回CPU寄存器和/或对my_int执行某些操作(例如d具有未定义的行为,并且可能会生成CPU陷阱/中断。

这不是C或C ++中的错误...它们旨在让您对硬件提出可疑请求,这样如果您知道自己在做什么,就可以做一些奇怪但有用的事情,很少需要使用汇编语言来编写低级代码,甚至是设备驱动程序和操作系统。

但是,正是因为在C ++中引入了更明确且有针对性的强制转换符号,强制转换可能是不安全的。不可否认风险 - 你只需要了解你所要求的东西,为什么它有时候是好的而不是其他的,并且与它一起生活。

答案 3 :(得分:2)

类型系统可以帮助,而不是照顾你。您可以通过多种方式绕过类型系统,不仅仅是关于const,而且每次执行此操作时,您正在从程序中获取一个安全性。您可以通过传递void*并根据需要进行转换来忽略const-correctness甚至基本类型系统。这并不意味着 const types 是谎言,只是你可以强迫你的方式通过编译器。

const是一种让编译器知道你的函数契约的方法,并让它帮助你不要违反它。与输入变量的方式相同,这样您就不需要猜测如何解释数据,因为编译器会帮助您。但它不会让宝宝坐下来,如果你强迫你的方式告诉它去除常量,或者如何检索数据,编译器会让你,毕竟你设计了应用程序,是谁再猜猜你的判断......

此外,在某些情况下,您实际上可能会导致未定义的行为,并且您的应用程序甚至可能会崩溃(例如,如果您从一个真正为const的对象中抛弃const并且您修改了该对象,您可能会发现副作用是在某些地方没有看到(编译器假定值不会改变并因此执行常量折叠)或者如果将常量加载到只读存储器页面中,您的应用程序可能会崩溃。

答案 4 :(得分:1)

从未const保证不变性:标准定义了允许修改const数据的const_cast

const对于您声明更多意图并避免更改意味着只读的数据非常有用。如果不这样做,您将收到编译错误,要求您三思而后行。你可以改变主意,但不建议这样做。

正如其他答案所提到的,如果你使用const-ness,编译器可能会进一步优化,但好处并不总是很重要。