一,前言
有关C++语言的const常量的考察点:肯定是const常量的内存不是分配在read-only的存储区的,const常量的内存分配区是很普通的栈或者全局区域。也就是说const常量只是编译器在编译的时候做检查,根本不存在什么read-only的区域。
所以说C++的const常量和常量字符串是不同的,常量字符串是存储在read-only的区域的,他们的具体的存储区域是不同的。
二,常量折叠
1,举例说明
常量折叠就是将常量表达式计算求值,并用求得的值来替换表达式,放入常量表。可以算作一种编译优化。(预编译阶段)
形象的例子,场景:老式的电影院,最后一排只能坐放映师,门口的检票员,一开始检票员手里没有对应表(常量/符号表),当有人进入电影院,检票员会将其姓名喝座位号写入对应表里,检票员不允许观众乱坐位置,每个人都必须坐在自己票上的位子。电影院里的每个观众席都是const常量类型,检票员就是编译器。
类似c++中,对于那些const变量,在预编译阶段就决定了他们在内存的存储位置,你如果直接改这个常量的值是不合法的。
但有个问题,观众进到电影院以后,我跟旁边的哥们说一声,我给你100块,你给我换一下位置吧,如果他同意了,我们就能换。检票员管不着,进都进来了,还能怎样。所以两个人就调换了位子。
同样,c++的const变量,编译器在编译的时候会进行语义检查,查看符号表,如果修改const的值会报错。但是绕过编译,在允许的时候修改const就会成功。
对于常量字符串的存储位置就不同了,它的存储位置是read-only的区域,就像电影院最后的那个放映师的位置,那里是一个特殊的位置,是真的不允许随便的调换。
2,理解
先说一个错误的理解:可折叠的常量就像宏一样,在预编译阶段对const常量的引用一律被替换为常量所对应的值。和普通的宏替换没有什么区别,并且编译器不会为该变量分配存储空间。
正确的理解是:常量确实会像宏替换一样,把对常量的引用替换为常量对应的值,但是该常量是分配空间的,并且考编译器来检查常量属性。
#define PI 3.14
int main(){
const int r = 10;
int p = PI; //这里发生宏替换,在预编译时替换
int len = 2*r; //这里发生常量折叠,常量r的引用会替换成对应的值,相当于len=2*10;
}
编译器会把const 常量放入到符号表中,同时,会为该变量分配空间,栈空间或者全局空间。既然放到了符号表中,就意味着可以找到这个变量的地址。
符号表不是一张表,是一系列表的统称,这里的const常量,会把这个常量的名字、类型、内存地址、值都放到常量表中。符号表还有一个变量表,这个表放变量的名字、类型、内存地址,但是没有放变量的值。
接下来我们通过一段代码来分析一下了解一下常量折叠及其特性:
int main()
{
const int a = 10;
int b = 2*a; //此处预编译的时候发生常量折叠,相当于b=2*10
int c = b;
int d = *(&a); //此处的*(&a),一般编译器在预编译时会对其优化成a。
int *p = (int*)&a; //此处对a取值操作,常量a是有内存的。
*p = 100;
printf("0x%p\n0x%p\n%d\n%d\n",&a,p,a,*p); //此处预编译的时候发生常量折叠,相当于
//printf("0x%p\n0x%p\n%d\n%d\n",&a,p,a,*p);
return 0;
}
1 编译器会为该常量在栈上分配一块空间。
const int a = 10;
0099446E mov dword ptr [a],0Ah
a是一个常量,按理说常量是存储在代码段的,可通过反汇编可以清楚地看到编译器确实为a在栈上分配了一块空间。
在这一点上const常量和宏是有区别的,宏在预编译阶段就完成了替换,编译器并不会为宏分配空间。
2 代码中遇上a的地方,直接替换为a的值
下面我们来比较一下常量折叠和正常变量的赋值
int b = 2*a;//常量折叠赋值
00994475 mov dword ptr [b],14h//真正的实现形式是int b=2*10
int c = b;//正常变量赋值
0099447C mov eax,dword ptr [b]//借助寄存器eax把b的值传递给c
0099447F mov dword ptr [c],eax
3指针p指向a在栈上的空间,而且明明通过*p=100对该空间上的内容作出了改变,为什么输出的a依旧是10呢?
话不多说,我们来看反汇编:
int *p = (int*)&a;
00994482 lea eax,[a] //通过寄存器eax把a的地址给指针p
00994485 mov dword ptr [p],eax
*p = 100;
00994488 mov eax,dword ptr [p]
0099448B mov dword ptr [eax],64h//修改p指向的空间的内容
最后说一下gcc编译的C语言的const常量,这里并没有做常量折叠的这种优化,类似于const常量前面加上volatile这个关键字。
const volatile int a = 10;