一、问题描述:
#include<stdio.h>
int main()
{
const int a = 10;
int *p = &a;
*p = 20;
printf("a = %d *p = %d\n",a,*p)
}
上面这段代码,输出为*p = 20,a = 10,可是从代码中我们可以明显看到*p指向的地址和变量a的地址是一样的,可是当我们给指针p所指向的内存地址赋值也就是*p = 20以后,相同地址的变量a的值却不变,到底是为什么呢?
二、解决过程
上网查询资料后,发现是编译器自动优化的,用下面这段代码就可以:
#include<stdio.h>
int main()
{
volatile const int a = 10;
int *p = &a;
*p = 20;
printf("a = %d *p = %d\n",a,*p)
}
上面这段代码的输出为:a = 20,*p = 20;果然用了volatile跳过编译器的自动优化后,就可以用指针改变const只读变量的值了。可是编译器是怎么优化的呢,我们知道,在内存中,给运行的程序按地址由低到高划分了五个区:代码区、常量区、静态区、栈区和堆区。也就是说,程序运行的时候,内存中是有存储常量的区域的,那么这里编译器是如何让cpu可以不从内存中拿数据的呢。
近来正在阅读《汇编语言(王爽)》和《计算机组成与设计 硬件/软件接口》,原来cpu只会去操作寄存器中的值,而不是直接从内存中取值,要操作内存(存储器)中数据,只会通过立即数取值、寄存器取值、基址取值、pc相对寻址、伪直接寻址,将内存中的数据读入寄存器后再进行相应的操作。
现在的cpu基本有两类寄存器,保留的(程序运行时,可以将数据保留在寄存器中)和不保留的(在程序运行的过程中,和内存不断交换数据)。保留的包括:全局指针,栈指针,帧指针,返回地址;不保留的包括:临时变量、参数、表达式求值和计算结果。
回到刚才的问题,const是将变量a定义为只读,那么编译器在编译(形成汇编语言)的时候就会将变量a用初始化的常量来代替,比如: 用mov R1 $10来代替move R1 (R2),R2中存储的是变量a的地址。这样R1这个寄存器是保留的,程序运行的过程中,不释放,这样就不用去内存中读取,直接从寄存器中取出。
同时给变量a一个块内存(在栈中)地址,在程序运行的时候,a其实是和a的那块内存是分开的,用变量a的时候用的是寄存器的数据,而不是内存中的数据,这样改变*q中的数据只是改变了内存中的数据,跟程序运行时a的值根本没有关系。