致力于创建我自己的编程语言,特别是支持指针和常量,我想知道常量如何存储在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;
}
Run Code Online (Sandbox Code Playgroud)
在这里,我定义一个被调用的常量x并从中创建一个指针,以便我可以修改它的值.这意味着x 不能存储在只读存储器中.
那么,我真的想知道常量是如何在运行时存储的?
通常,特别是在打开优化时,编译器将尝试使常量尽可能高效.例如,如果编写x = 3*4 + 5,编译器会在编译时将其减少到17,而不是在编译的程序中放入3,4和5.
直接使用的小常量通常被编码为直接的指令字段.
如果编译器不能将常量放入指令的立即字段,它通常会寻求将其存储在只读存储器中.
但是,您提供的示例使编译器难以实现.您的示例中的代码:
const例程内的对象(而不是文件范围).如果您只是定义了一个const对象而从未获取其地址,则编译器可以将该常量存储在只读数据部分中.
但是,由于您获取对象的地址,因此存在问题.例程可以递归调用.(您的程序不会main递归调用,但编译器旨在支持递归调用,因此此处讨论的问题适用于其设计.)每当递归调用例程时,必须创建在其中定义的对象的新实例(在C计算模型).如果const没有采用对象的地址,编译器可以通过对对象的所有实例使用相同的只读内存来优化它 - 因为它们的值永远不会改变,没有人能够告诉它们只是一个实例而不是多个副本.
但是,对象的不同实例可以通过它们的地址来区分.由于您获取对象的地址,编译器希望创建它的实际不同实例.在只读内存中很难做到.程序通常不为只读内存维护堆栈,因此编译器没有方便的方法来跟踪必须为只读内存中的对象创建的多个实例.(为只读内存维护堆栈是很困难的.如果堆栈上的内容在不同时间可能不同,那么该堆栈的内存必须改变.因此,即使只有只读对象堆栈,堆栈本身不能只读.)
因此,在这种情况下,编译器将您的const对象放在常规堆栈上.
当然,这不是您可能依赖的行为.尝试更改已定义对象的值的const行为未由C标准定义.即使它在这种情况下似乎"有效",但在更复杂的程序中,编译器可能会使用各种优化来转换程序,结果是在尝试修改这样的const对象时程序会失败.例如,printf("%d", *y)可以打印"2",因为内存y点已经更改为2,而printf("%d", x)可以打印"1",因为x已知(在C计算模型中)为常数1.