代码之家  ›  专栏  ›  技术社区  ›  ClementNerma

C中的常量是如何存储在内存中的?

  •  3
  • ClementNerma  · 技术社区  · 8 年前

    致力于创建自己的编程语言,尤其是支持指针和常量的编程语言,我想知道常量是如何存储在C等语言的内存中的?我在StackOverflow上读到,它们在运行时存储在只读内存中,但我不明白这怎么可能,因为以下代码编译和执行得很好:

    #include <stdio.h>
    
    int main (int argc, char ** argv) {
      const int x = 1;
    
      int *y;
      y = &x;
    
      *y += 1;
    
      printf("x = %d\n", x); // Prints: 2
      printf("y = %d\n", *y); // Prints: 2
    
      return 0;
    }
    

    这里,我定义了一个常量 x 并从中创建一个指针,以便修改其值。这意味着 十、 不能是 存储在只读存储器中。

    所以,我真的很想知道常量在运行时是如何存储的?

    4 回复  |  直到 8 年前
        1
  •  5
  •   Eric Postpischil    8 年前

    通常,尤其是在启用优化时,编译器会尝试使常量尽可能有效。例如,如果您编写 x = 3*4 + 5 ,编译器将在编译时将其减少到17,而不是在编译程序中放入3、4和5。

    直接使用的小常量通常被编码到指令的直接字段中。

    如果编译器无法将常量放入指令的立即数字段,它通常会将其存储在只读内存中。

    然而,您给出的示例使编译器很难做到这一点。示例中的代码:

    • 定义 const 对象(而不是在文件范围内)。
    • 获取对象的地址。

    如果您仅定义 常量 对象,并且从不获取其地址,编译器可以将常量存储在只读数据节中。

    但是,由于您获取了对象的地址,因此存在一个问题。例程可以递归调用。(您的程序不调用 main 递归,但编译器设计为支持递归调用,因此此处讨论的问题适用于其设计。)每当递归调用例程时,必须创建其中定义的对象的新实例(在C计算模型中)。如果 常量 对象,编译器可以通过为对象的所有实例使用相同的只读内存对此进行优化,因为它们的值永远不会改变,没有人知道它们只是一个实例而不是多个副本。

    但是,对象的不同实例可以通过其地址来区分。由于您获取对象的地址,编译器希望创建它的实际不同实例。在只读存储器中很难做到这一点。程序通常不会为只读内存维护堆栈,因此编译器无法方便地跟踪必须为只读内存中的对象创建的多个实例。(很难为只读内存维护堆栈。如果堆栈上的内容在不同时间可能不同,则该堆栈的内存必须更改。因此,即使堆栈上只有只读对象,堆栈本身也不能是只读的。)

    因此,在本例中,编译器将 常量 常规堆栈上的对象。

    当然,这不是你可以信赖的行为。试图更改已定义对象的值 常量 具有C标准未定义的行为。尽管在这种情况下看起来很有效,但在更复杂的程序中,编译器可能会通过各种优化来转换您的程序,从而导致您的程序在尝试修改 常量 像这样的对象。例如 printf("%d", *y) 无法打印–2–157;,因为内存 y 指向的点已更改为2,而 printf("%d", x) 无法打印1,因为 x 已知(在C计算模型中)为常数1。

        2
  •  2
  •   Steve Summit    8 年前

    您发现了一个关于C中未定义行为的悲惨事实。

    “编译和执行良好”确实 (非不可)证明该代码合法且正确!

    如果你作弊,试着写信给 const -合格的位置,任何事情都可能发生:你可以得到一条错误消息,它可能看起来有效,或者悄悄地做一些几乎或完全不同于你预期的事情。

    当你说

    y = &x;
    

    您的编译器应该已经警告过您,“警告:从‘const int*’分配给‘int*’会丢弃限定符”。(这正是我的编译器所说的。)

    因此,是的,编译器完全有权在文本段或只读内存的其他部分中存储不可修改的常量。如果这意味着像你这样的程序失败了,那也没关系。

    但还有一点。您声明的“常量”, const int x = 1 ,是一个 地方的 变量因此,每次调用函数都需要一个新的实例,这意味着它可能存储在某种堆栈上。这意味着编译器可能 不是的 将要放置 x 在只读内存中,因为我从未听说堆栈上有只读内存。

    如果你 十、 通过声明全局变量 外部 属于 main() ,或者如果您将 static 在它前面,它不会存储在任何堆栈上,而且更可能存储在只读内存中。事实上,当我对您的代码进行这两项更改中的任何一项时,我不仅在编译时收到相同的警告,而且在运行程序时,它会因“总线错误”而崩溃。

        3
  •  1
  •   Soner from The Ottoman Empire    8 年前

    好吧,它们的存储方式取决于编译器的实现细节。然而,它通常存储在文本部分。

    顺便说一句 const 如果 常量 要更改变量。

        4
  •  0
  •   Luis Colorado    8 年前

    A. const 对象是编译器不允许赋值的变量。因此,它可以应用于address操作符来获取其地址,也可以像使用变量一样使用它(或者甚至是左值,当然是类型 常量 )但是 您不能修改它 (至少使用正常的分配程序)。允许编译器(对于静态 常量 s) 将它们存储在只读存储器中,但这不是标准规定的。这是一个快速的答案。有关更多详细信息,请参阅标准。

    在您发布的情况下,当您使用 &x 表达式,你得到 const int* 值,自动转换为 int * 指针(可能您正在为此收到警告,而该警告将被忽略)

    $ make pru
    cc -O -pipe  pru.c  -o pru
    pru.c:7:5: warning: assigning to 'int *' from 'const int *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
      y = &x;
        ^ ~~
    1 warning generated.
    

    出于遗留代码兼容性的原因,这被视为警告而不是错误,因此您可以自由采取相应的行动(在本例中,您没有这样做:))