26

可能重复:
向我推销 const 正确性

const关键字在CC++既然允许这样的事情有什么用处?

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不能保证论点的不可修改性。

4

5 回答 5

45

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()违反了其合同,因此发生了奇怪的事情。不要违反合同。并且很高兴编译器可以帮助您遵守合同。演员是邪恶的。

于 2012-06-05T23:59:40.760 回答
10

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

如果您告诉编译器删除const限定符,它会很乐意这样做。如果你让它完成它的工作,编译器将帮助确保你的代码是正确的。通过抛弃 const-ness,您是在告诉编译器您知道 的目标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 限定符 [dcl.type.cv]

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

“医生,我这样做的时候很痛!” “所以不要那样做。”

于 2012-06-06T00:02:31.947 回答
6

您的...

int n = 1;

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

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

但是,这应该不足为奇。类型的情况相同-如果您有...

double d = 1;
*(int*)&d = my_int;
d += 1;

...您是否对编译器的类型撒谎d?最终d占用的内存可能在硬件级别上可能是无类型的,因此编译器所拥有的只是对它的看法,将位模式进出混洗。但是,根据硬件上的值my_int和双精度表示,您可能创建了d不代表任何有效双精度值的无效位组合,以便后续尝试将内存读回 CPU 寄存器和/或者做一些事情,d比如+= 1有未定义的行为,例如,可能会产生 CPU 陷阱/中断。

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

尽管如此,正是因为强制转换可能是不安全的,所以在 C++ 中引入了更明确和更有针对性的强制转换表示法。不可否认的风险 - 你只需要了解你所要求的,为什么有时可以而不是其他人,并忍受它。

于 2012-06-06T01:42:27.497 回答
2

类型系统可以帮助您,而不是照看您。您可以通过多种方式规避类型系统,不仅涉及 const,而且每次您这样做时,您所做的就是从程序中获取一个安全性。void*您可以根据需要通过传递和强制转换来忽略 const 正确性甚至基本类型系统。这并不意味着consttypes是谎言,只是你可以强行超越编译器。

const有没有一种方法可以让编译器知道你的函数的契约,并让它帮助你不违反它。与输入变量的方式相同,因此您无需猜测如何解释数据,因为编译器会帮助您。但它不会坐以待毙,如果你强行告诉它删除 const-ness,或者如何检索数据,编译器只会让你,毕竟你设计了应用程序,它是谁第二个猜你的判断...

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

于 2012-06-06T03:20:18.193 回答
1

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

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

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

于 2012-06-06T00:03:13.337 回答