可能重复:
向我推销 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 正确性
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 <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()
违反了其合同,因此发生了奇怪的事情。不要违反合同。并且很高兴编译器可以帮助您遵守合同。演员是邪恶的。
在这种情况下,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 对象的尝试都会导致未定义的行为
“医生,我这样做的时候很痛!” “所以不要那样做。”
您的...
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++ 中引入了更明确和更有针对性的强制转换表示法。不可否认的风险 - 你只需要了解你所要求的,为什么有时可以而不是其他人,并忍受它。
类型系统可以帮助您,而不是照看您。您可以通过多种方式规避类型系统,不仅涉及 const,而且每次您这样做时,您所做的就是从程序中获取一个安全性。void*
您可以根据需要通过传递和强制转换来忽略 const 正确性甚至基本类型系统。这并不意味着const或types是谎言,只是你可以强行超越编译器。
const
有没有一种方法可以让编译器知道你的函数的契约,并让它帮助你不违反它。与输入变量的方式相同,因此您无需猜测如何解释数据,因为编译器会帮助您。但它不会坐以待毙,如果你强行告诉它删除 const-ness,或者如何检索数据,编译器只会让你,毕竟你设计了应用程序,它是谁第二个猜你的判断...
此外,在某些情况下,您实际上可能会导致未定义的行为,您的应用程序甚至可能崩溃(例如,如果您从一个真正为 const 的对象中丢弃 const 并修改该对象,您可能会发现在某些地方(编译器假定该值不会更改,因此执行了常量折叠),或者如果将常量加载到只读内存页面中,您的应用程序可能会崩溃。
从未const
保证不变性:标准定义了const_cast
允许修改 const 数据的 a。
const
对您声明更多意图并避免更改您打算只读的数据很有用。如果您不这样做,您将收到一个编译错误,要求您三思而后行。您可以改变主意,但不建议这样做。
正如其他答案所提到的,如果您使用 const-ness,编译器可能会优化更多,但好处并不总是很重要。