【问题标题】:Can we change the value of an object defined with const through pointers?我们可以通过指针更改用 const 定义的对象的值吗?
【发布时间】:2011-04-17 14:59:09
【问题描述】:
#include <stdio.h>
int main()
{
    const int a = 12;
    int *p;
    p = &a;
    *p = 70;
}

会有用吗?

【问题讨论】:

标签: c constants undefined-behavior


【解决方案1】:

它确实适用于 gcc。但它不喜欢它:

test.c:6: 警告:赋值会丢弃指针目标类型的限定符

但值在执行时确实发生了变化。我不会指出明显的禁忌...

【讨论】:

    【解决方案2】:

    这是“未定义的行为”,这意味着根据标准,您无法预测尝试此操作时会发生什么。根据特定的机器、编译器和程序的状态,它可能会做不同的事情。

    在这种情况下,最常见的情况是答案是“是”。变量,无论是否为 const,只是内存中的一个位置,您可以打破 const 的规则并简单地覆盖它。 (当然,如果程序的某些其他部分依赖于其 const 数据是常量,这将导致严重的错误!)

    但是在某些情况下——最典型的是const static 数据——编译器可能会将这些变量放在内存的只读区域中。比如 MSVC,通常会将 const static ints 放在可执行文件的 .text 段中,这意味着如果你试图写入它,操作系统会抛出一个保护错误,程序会崩溃。

    在编译器和机器的其他组合中,可能会发生完全不同的事情。您可以确定的一件事是,这种模式会惹恼任何必须阅读您的代码的人。

    【讨论】:

    • 我在 ideone 中尝试过类似的代码,但它崩溃了。是否有可靠的方法来实际更改变量?
    • 由于该值被假定为常量,编译器还可以通过将该值用作指令中的立即值而不是使用额外的加载指令从内存中获取它来优化内存访问。所以你可以在这里得到很多奇怪的状态:变量被优化器完全消除,在某些地方使用引用变量,但在其他地方使用立即数(你的 hack 没有改变)等等。(我是不确定 const 仅部分更改为立即数的可能性有多大,但我很确定立即替换是可能的)。
    【解决方案3】:

    您不能通过使用指向它的指针来更改常量变量的值。这种类型的指针称为Pointer to a constant

    还有另一个概念叫做Constant Pointer。这意味着一旦一个指针指向一个内存位置,你就不能让它指向另一个位置。

    【讨论】:

      【解决方案4】:

      通过指针修改const 限定对象会调用未定义的行为,结果就是这样。这可能是您对特定实现的期望,例如之前的值不变,如果已经放在.text等中。

      【讨论】:

      • 另一个可能的结果是“某些表达式的行为就像 a 仍然是 12,而其他表达式的行为就像 a 现在是 70”,这可能是也可能不是“你所期望的"。
      【解决方案5】:

      是的,您可以使用这样的代码来完成。但是当a 是全局时,代码不适用(gcc 编译的程序给了我segmentation fault。)

      一般来说,在心爱的 C 中,您几乎总能找到一些方法来破解不应该更改或公开的内容。 const 这里是一个例子。

      但想想可怜的人(可能是我自己 6 个月后)维护我们的代码,我经常选择不这样做。

      【讨论】:

        【解决方案6】:

        这里的指针p 的类型是int*,它被分配了const int* 类型的值(&amp;a => const int 变量的地址)。

        隐式转换消除了常量,尽管 gcc 会引发警告(请注意,这在很大程度上取决于实现)。

        由于指针未声明为const,因此可以使用此类指针更改值。

        如果将指针声明为const int* p = &amp;a,您将无法执行*p = 70

        【讨论】:

          【解决方案7】:

          这是未定义的行为。证明:

          /* program.c */
          
          int main()
          {
                  const int a = 12;
                  int* p;
                  p = &a;
                  *p = 70;
                  printf("%d\n", a);
                  return 0;
          }
          
          
          gcc program.c
          

          并运行它。输出将是 70 (gcc 4.3)

          然后像这样编译它:

          gcc -O2 program.c
          

          并运行它。输出将是 12。当它进行优化时,编译器可能会将 12 加载到寄存器中,并且当它需要访问 printf 的 a 时不会费心再次加载它,因为它“知道” a 不能改变。

          【讨论】:

          • 有趣的是,当我运行这个程序时,输出是 42。
          • @abelenky:是的,但你有没有停下来思考问题是什么?也许你应该造一台电脑来找出答案。
          • gcc 在给定一段代码时表现异常的事实仅意味着如果 gcc 在每种情况下都支持标准,则代码会调用未定义行为。然而,在实践中,gcc 无法有效地处理符合编译器所需的某些语义,而不是以符合但低效的方式处理它们,或者修复它们的执行模型以高效和符合的方式处理它们,而是 gcc 操作以一种“有效”但不合格且不合格的方式处理它们。
          • @supercat 在这种情况下,gcc 的行为符合标准。使用指针写入const 对象是UB。它是 UB 的原因是,在实践中,如果没有操作系统级别的内存保护,很难强制执行逻辑正确的行为。
          • 证明 UB 将引用标准,而不是检查代码输出
          【解决方案8】:

          糟糕,糟糕的想法。

          此外,行为是特定于平台和实现的。如果您在常量存储在不可写内存中的平台上运行,这显然行不通。

          而且,你到底为什么要这样做?要么更新源中的常量,要么将其设为变量。

          【讨论】:

            【解决方案9】:

            是的,您可以更改常量变量的值。
            试试这个代码:

            #include <stdio.h>
            
            int main()
            {
                const  int x=10;
            
                int *p;
                p=(int*)&x;
                *p=12;
                printf("%d",x);
            }
            

            【讨论】:

              【解决方案10】:

              此代码包含约束冲突

              const int a = 12;
              int *p;
              p = &a;
              

              违反的约束是 C11 6.5.16.1/1“简单赋值”;如果两个操作数都是指针,则左指向的类型必须具有右指向的类型的所有限定符。 (并且类型,无限定符,必须兼容)。

              因此违反了约束,因为&amp;a 的类型为const int *,其中const 作为限定符;但该限定符不会出现在p 类型中,即int *

              编译器必须发出诊断信息,并且可能不会生成可执行文件。任何可执行文件的行为都是完全未定义的,因为程序不符合语言规则。

              【讨论】:

                【解决方案11】:

                更改const 变量的值的问题是编译器不会期望发生这种情况。考虑这段代码:

                const int a = 12;
                int * p = &a;
                *p = 70;
                printf("%d\n", a);
                

                为什么编译器会在最后一条语句中读取a?编译器知道a12 并且因为它是const,所以它永远不会改变。所以优化器可能会将上面的代码转换成这样:

                const int a = 12;
                int * p = &a;
                *p = 70;
                printf("%d\n", 12);
                

                这可能会导致奇怪的问题。例如。代码在没有优化的调试版本中可能会按需要运行,但在经过优化的发布版本中会失败。

                实际上,一个好的优化器可能会将整个代码转换成这样:

                printf("%d\n", 12);
                

                因为之前的所有其他代码在编译器眼中都没有影响。省略没有效果的代码也不会影响整个程序。

                另一方面,一个体面的编译器会识别出你的代码有问题并警告你,因为

                int * p = &a;
                

                实际上是错误的。正确的是:

                const int * p = &a;
                

                因为p 不是指向int 的指针,它是指向const int 的指针,当这样声明时,下一行将导致硬编译错误。

                要消除警告,您必须强制转换:

                int * p = (int *)&a;
                

                更好的编译器会识别出这种转换违反了const 的承诺,并指示优化器不要将a 视为const

                如您所见,编译器的质量、功能和设置将最终决定您可以期待什么行为。这意味着相同的代码在不同的平台上或在同一平台上使用不同的编译器时可能会表现出不同的行为。

                如果 C 标准为这种情况定义了一个行为,所有编译器都必须实现它,无论标准定义什么,它都很难实现,给每个想要编写一个编译器。即使标准刚刚说“这是禁止的”,所有编译器都必须执行复杂的数据流分析才能强制执行此规则。所以标准只是没有定义它。它定义了const 的值不能更改,如果您无论如何都找到了更改它们的方法,那么您将无法依赖任何行为。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2021-08-05
                  • 1970-01-01
                  • 2021-05-20
                  • 2019-12-06
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多