常见谎言? (因为const可以被抛弃)
可能重复:
卖我的正确性
关键字const
在C
或C++
的用处是什么,因为它允许这样的事情?
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
不能保证论证的不可修改性。
const
是对编译器的承诺,而不是它保证的东西。
例如,
void const_is_a_lie(const int* n) { *((int*) n) = 0; } #include 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()
违反了合同,因此会发生奇怪的事情。 不要违反合同。 并且很高兴编译器能帮助您保持合同。 演员是邪恶的。
在这种情况下, 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)都会导致未定义的行为
“医生,我这样做会很疼!” “所以不要这样做。”
您的…
int n = 1;
…确保读/写内存中存在n
; 它是一个非常量变量,因此稍后修改它的尝试将具有已定义的行为。 给定这样一个变量,你可以混合使用const
和/或非常量指针以及对它的引用 – 每个指针的const
只是程序员防止意外改变代码“分支”的一种方式。 我说“分支”是因为您可以将给定n
的访问可视化为树,其中 – 一旦分支被标记为const
,所有子分支(进一步指向/引用n
是否有其他局部变量,函数参数等初始化需要保持const
,除非你明确表达了const
概念。 对于像n
一样可变的变量,抛弃const
是安全的(如果可能令人困惑),因为它们最终仍然写回到可修改/可变/非常量的内存地址。 您可以想象在这些情况下可能会遇到麻烦的所有奇怪的优化和缓存都不允许,因为标准要求并保证在我刚刚描述的情况下的理智行为。
遗憾的是,也可以抛弃真正固有的const
变量的const int o = 1;
比如说const int o = 1;
,任何修改它们的尝试都会有不确定的行为。 这有很多实际的原因,包括编译器将它们放在内存中然后标记为只读的权利(例如参见UNIX mprotect(2)
),这样尝试写入将导致CPU陷阱/中断,或者每次读取变量需要最初设置的值(即使在代码中从未使用该值提及变量的标识符),或者使用原始值的内联编译时副本 – 忽略对变量本身的任何运行时更改。 因此,标准使行为未定义。 即使它们碰巧按照您的意图进行了修改,程序的其余部分也将具有未定义的行为。
但是,这不应该是令人惊讶的。 类型的情况也是如此 – 如果你有……
double d = 1; *(int*)&d = my_int; d += 1;
…你有没有向编译器撒谎关于d
的类型? 最终d
占用的内存可能在硬件级别上是无类型的,因此编译器所拥有的只是对它的一种观点,进而改变位模式。 但是,根据my_int
的值和硬件上的双重表示,您可能在d
中创建了无效的位组合,这些位组合不代表任何有效的double值,以便后续尝试将内存读回CPU寄存器和/或用d
做某些事如+= 1
有未定义的行为,例如,可能会产生CPU陷阱/中断。
这不是C或C ++中的错误…它们旨在让您对硬件提出可疑请求,这样如果您知道自己在做什么,就可以做一些奇怪但有用的事情,很少需要依赖汇编语言编写低级代码,甚至用于设备驱动程序和操作系统。
但是,正是因为在C ++中引入了更明确和有针对性的强制转换符号,强制转换可能是不安全的。 不可否认风险 – 你只需要了解你所要求的东西,为什么它有时候是好的而不是其他的,并且忍受它。
类型系统是帮助,而不是照顾你。 你可以通过多种方式规避类型系统,不仅仅是关于const,每次你这样做,你正在做的是从你的程序中取出一个安全。 您可以通过传递void*
around并根据需要进行转换来忽略const-correctness甚至基本类型系统。 这并不意味着const或类型是谎言,只是你可以强行通过编译器。
const
是一种让编译器知道你的函数契约的方法,并让它帮助你不要违反它。 与输入变量的方式相同,因此您无需猜测如何解释数据,因为编译器会帮助您。 但它不会让宝宝坐下来,如果你强迫你的方式告诉它去除常量,或者如何检索数据,编译器会让你,毕竟你设计了应用程序,是谁第二个猜你的判断……
此外,在某些情况下,您实际上可能会导致未定义的行为,并且您的应用程序甚至可能会崩溃(例如,如果您从一个真正为const的对象中抛弃const并且您修改了该对象,您可能会发现副作用未在某些地方(编译器假定该值不会更改并因此执行常量折叠)或者如果将常量加载到只读内存页中,您的应用程序可能会崩溃。
const
没有保证不变性:标准定义了一个允许修改const数据的const_cast
。
const
对于您声明更多意图并避免更改您想要只读的数据非常有用。 如果不这样做,您将收到编译错误,要求您三思而后行。 你可以改变主意,但不建议这样做。
正如其他答案所提到的,如果你使用const-ness,编译器可能会进一步优化,但好处并不总是很重要。