康斯特是谎言吗?(因为康斯特可以被抛弃)

Is const a lie? (since const can be cast away)

本文关键字:康斯特 抛弃 谎言 因为      更新时间:2023-10-16

可能的重复项:
卖给我关于常量正确性

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

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限定符,它会很乐意这样做。 编译器将帮助确保您的代码正确,如果您让它完成其工作。 通过抛弃常量,你告诉编译器你知道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("%sn", text);
    return 0;
}

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

该标准对修改常量对象有这样的说法。

7.1.6.1/4 简历限定符 [dcl.type.cv]

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

"医生,我这样做很痛!" "所以不要那样做。"

你的...

int n = 1;

。确保n存在于读/写存储器中;它是一个非const变量,因此以后尝试修改它将具有定义的行为。 给定这样一个变量,你可以混合使用const和/或非const指针和对它的引用 - 每个变量的恒定性只是程序员防止代码"分支"意外更改的一种方式。 我之所以说"分支",是因为您可以将n的访问可视化为一棵树,一旦分支被标记为const,所有子分支(进一步的指针/引用n是否从中初始化的其他局部变量、函数参数等)将需要保持const,除非你明确地抛弃了恒定性的概念。 对于像n这样可变的变量来说,抛弃const是安全的(如果可能令人困惑),因为它们最终仍然写回可修改/可变/非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 寄存器和/或对d执行某些操作(例如+= 1具有未定义的行为和可能, 例如,生成 CPU 陷阱/中断。

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

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

类型系统是为了提供帮助,而不是照顾你。你可以用很多方式绕过类型系统,不仅仅是关于 const,而且每次你这样做时,你所做的就是从你的程序中取出一个安全性。您可以通过传递void*并根据需要进行转换来忽略常量正确性甚至基本类型系统。这并不意味着 const类型是谎言,只是你可以强行超越编译器。

const作为一种让编译器知道你的函数的协定的方法,并让它帮助你不违反它。就像正在键入的变量一样,这样你就不需要猜测如何解释数据,因为编译器会帮助你。但它不会坐着,如果你强迫它删除常量,或者如何检索数据,编译器只会让你,毕竟你设计了应用程序,谁来猜测你的判断......

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

const从来没有保证过不可变性:该标准定义了一个允许修改常量数据的const_cast

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

正如其他答案所提到的,如果您使用常量,编译器可能会进行更多优化,但好处并不总是显着的。