【问题标题】:Is const a lie? (since const can be cast away) [duplicate]const 是谎言吗? (因为 const 可以被抛弃)[重复]
【发布时间】:2012-06-05 23:57:05
【问题描述】:

可能重复:
Sell me on const correctness

CC++ 中的关键字 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 有时是 未定义的行为
  • @K-ballo 我不明白为什么会有这么多好的答案——比如你的! - 作为评论…
  • cast 一切皆有可能:)
  • @K-ballo 是的,UB 来自作业。
  • 这不是重复的。他问为什么要使用const,如果你可以抛弃它,另一个问题是“我为什么要使用 const 而不是不改变东西”

标签: c++ c constants


【解决方案1】:

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

【讨论】:

  • 特别是,C 编译器可以合法地(并且合理地)得出结论:n 永远不会改变(它是main 的本地,永远不会在main 中修改,并且没有非常量引用从main),所以不断折叠 printf 以打印 1,即使 n 不是 const
  • @ChrisDodd:不,我认为不可能。转义的引用是 const-qualified 的事实是不相关的 - 对非常量对象的引用会转义,这才是最重要的。您在标准中是否有参考来支持您的观点?
  • 在非 const 对象的引用上抛弃 const 不是 UB,所以我不确定这是否真的是真的。
  • @DeadMG 事实上,抛弃 constness 永远不是 UB。 修改一个const对象只是UB。
  • @fbafelipe: const 通常作为一种文档形式很有用......但不是你给出的例子。如果一个对象由原始指针返回,const 与否,调用者不应该删除它。在现代 C++ 风格中,如果所有权正在转移,对象会以 std::unique_ptr 的形式返回并携带其删除器。
【解决方案2】:

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

如果您告诉编译器删除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 限定符 [dcl.type.cv]

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

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

【讨论】:

    【解决方案3】:

    你的……

    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++ 中引入了更明确和更有针对性的强制转换表示法。不可否认的风险 - 你只需要了解你所要求的,为什么有时可以而不是其他的,并忍受它。

    【讨论】:

      【解决方案4】:

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

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

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

        • 仅供阅读本文的任何人使用,const_cast 仅存在于 C++ 中,而不存在于 C 中。
        • 哦,确实,我错过了“C”标签。
        • const_cast 只是删除了 cv 限定符。该标准仍然指出“在其生命周期内修改 const 对象会导致未定义的行为”。据我所知,它没有声明删除 cv-qualifier 会释放对象进行修改。
        • @ChetSimpson Hum ...我没有标准也没有时间精确搜索它,但我认为这是“可能”触发 UB,而不是“意志”。 msdn.microsoft.com/en-us/library/bz6at95h(v=vs.100).aspx.
        • @ChetSimpson 经过一番思考后我认为您可以修改const object 但不能修改const data . IE。一个对象可以是const 并且有一个mutable 字段或者是const_cast。永远不能修改常量(如const int i = 3)。如何做到这一点可能有更精确的条件,但我没有时间去挖掘更多。
        猜你喜欢
        • 1970-01-01
        • 2012-09-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多