可能是重复问题:
说服我使用const正确性
C
或C++
中的关键字const
有什么用处,既然它允许这样的事情?
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正确性
C
或C++
中的关键字const
有什么用处,既然它允许这样的事情?
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()
违反了约定,所以会发生奇怪的事情。请不要违反合同,并且感谢编译器帮助您遵守合同。强制转换是有害的。
const
限定符并不重要 - 只要一个非常量对象的引用逃逸了,这就是最重要的。您是否有标准中支持您观点的参考文献? - cafn
是一个指向常量 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)中尝试修改其内容都会导致未定义行为(undefined behavior)。
"医生,我这样做会很疼!" "那就别这么做了。"
Your...
int n = 1;
确保n
存在于读写内存中;它是一个非const
变量,因此稍后尝试修改它将具有定义的行为。有了这样一个变量,您可以拥有对它的const
和/或非const
指针和引用的混合 - 每个的常数性只是程序员防止在该代码“分支”中意外更改的一种方式。我说“分支”,因为您可以将访问n
所给予的视为一棵树,其中-一旦标记了const
的分支,则所有子分支(进一步指向/引用n
的指针/引用,无论是否从中初始化了其他本地变量、函数参数等)都需要保持const
,除非您明确地取消该常数性概念。取消const
是安全的(如果可能会令人困惑),对于像您的n
这样的可变变量,因为它们最终仍然写回到一个可修改/可变/非const
的内存地址中。在这些情况下,你可以想象所有奇怪的优化和缓存都不允许发生问题,因为标准要求并保证在我刚才描述的情况下有明智的行为。
不幸的是,也有可能取消真正固有const
变量的常数性,比如说const int o = 1;
,任何尝试修改它们的企图都将具有未定义的行为。这有许多实际原因,包括编译器将它们放置在只读内存中(例如UNIX mprotect(2)
),以便尝试写入时会导致CPU陷阱/中断,或者每当需要原始设置值时从变量中读取(即使没有在使用该值的代码中提到变量的标识符),或者使用原始值的编译时内联副本-忽略变量本身的任何运行时更改。因此,标准将行为留给了未定义。即使它们按您的意图被修改,程序的其余部分之后也将具有未定义的行为。
但是,这不应该令人惊讶。对于类型也是同样的情况-如果你有...
double d = 1;
*(int*)&d = my_int;
d += 1;
你有没有欺骗编译器关于d
类型的信息?最终,d
占用的内存在硬件级别上可能是未分类的,因此编译器对其的看法仅限于对其进行位模式的重排。但是,根据my_int
的值和您的硬件上的双精度表示,您可能已创建d
中的无效位组合,这些组合不代表任何有效的双精度值,因此尝试将内存读回到CPU寄存器中和/或执行类似+= 1
的操作时,具有未定义的行为并且可能会例如生成CPU陷阱/中断。
这不是C或C ++中的错误...它们被设计为让您向硬件发出可疑请求,以便如果您知道自己在做什么,则可以执行一些奇怪但有用的操作,并且很少需要退回到汇编语言编写低级代码,即使是设备驱动程序和操作系统也是如此。
尽管如此,正是因为转换可能是不安全的,因此在C ++中引入了更明确和有针对性的转换符号。无法否认风险-您只需要理解您正在要求什么,为什么有时可以,而有时则不行,并且学会接受它。
void*
并根据需要进行转换。这并不意味着const或者类型是虚假的,只是你可以强制编译器按照你的方式执行。
const
的作用在于让编译器知道你的函数契约,并且让它帮助你避免违反它。同样,变量被赋予类型也是为了让你不需要猜测如何解释数据,因为编译器会帮助你。但是它不会像保姆一样,如果你强制删除const属性或告诉它如何检索数据,编译器就会放任你,毕竟是你设计了应用程序,谁能怀疑你的判断力呢...“const”从未保证不可变性:标准定义了一个“const_cast”,允许修改常量数据。
“const”对于您声明更多意图并避免更改仅用于读取的数据非常有用。如果您这样做,您将获得编译错误提示您三思而后行。您可以改变主意,但不建议这样做。
正如其他答案所提到的,编译器可能会在使用常数时进行更多优化,但好处并不总是显着的。
const_cast
仅存在于 C++ 中,而非 C。 - cdhowieconst_cast
仅仅是删除cv限定符。标准仍然规定“在其生命周期内修改const对象会导致未定义的行为”。据我所知,没有任何地方规定删除cv限定符会释放对象以进行修改。 - Captain Obvliousconst
对象,但不能修改const
数据。例如,一个对象可以是const
并且有一个mutable
字段或者使用const_cast
。一个常量(如const int i = 3
)绝不能被修改。可能有更精确的条件来解释这一点,但我没有时间深入挖掘。 - J.N.
const
,如果你可以将其转换掉,另一个问题是“为什么我应该使用const而不是只是不改变东西”。 - CoffeeTableEspresso