本文转载自http://blog.csdn.net/yby4769250/article/details/7359278
今天回顾了大学这几年学习C++的点点滴滴,在回顾了“常量折叠”这里时,突然困惑了,当时学习这点知识时的理解是:可折叠的常量像宏一样,在预编译阶段对常量的引用一律被替换为常量所对应的值,就和普通的宏替换没什么区别,并且,编译器不会为该常量分配空间。现在回顾起来,当时是多么的天真,被现象迷惑了,常量折叠确实会像宏一样把对常量的引用替换为常量对应的值,但是,并非不给该常量分配空间,如下代码
-
#define PI 3.14 int main() { const int r = 10; int p = pI; //这里会在预编译阶段产生宏替换,PI直接替换为3.14,其实就是int p = 3.14; int len = 2*r; //这里会发生常量折叠,也就是对常量r的引用会替换成他对应的值,相当于int len = 2*10; return 0; }
如上述代码中所述,常量折叠表面上的效果和宏替换是一样的,只是,“效果上是一样的”,而两者真正的区别在于,宏是字符常量,在预编译完宏替换完成后,该宏名字会消失,所有对宏如PI的引用已经全部被替换为它所对应的值,编译器当然没有必要再维护这个符号。而常量折叠发生的情况是,对常量的引用全部替换为该常量如r的值,但是,常量名r并不会消失,编译器会把他放入到符号表中,同时,会为该变量分配空间,栈空间或者全局空间。
为了能更清楚的体现出常量折叠,下面做几个对照实验,看代码和输出便了然:
int main()
{ int i0 = 11; const int i=0; //定义常量i int *j = (int *) &i; //看到这里能对i进行取值,判断i必然后自己的内存空间 *j=1; //对j指向的内存进行修改 printf("%d\n%d\n%d\n%d\n",&i,j,i,*j); //观看实验效果 const int ck = 9; //这个对照实验是为了观察,对常量ck的引用时,会产生的效果 int ik = ck; int i1 = 5; //这个对照实验是为了区别,对常量和变量的引用有什么区别 int i2 = i1; return 0; }
上面的代码会输出:
0012ff7c
0012ff7c
0
1
这能说明什么,至少能说明两点:
1、ij地址相同,指向同一块空间,i虽然是可折叠常量,但是,i确实有自己的空间
2、ij指向同一块内存,但是*j = 1对内存进行修改后,按道理来说,*j==1,i也应该等于1,而实验结果确实i实实在在的等于0,这是为什么呢,就是本文所说的内容,i是可折叠常量,在编译阶段对i的引用已经别替换为i的值了,也就是说
-
printf("%d\n%d\n%d\n%d\n",&i,j,i,*j)
中的i已经被替换,其实已经被改为
-
printf("%d\n%d\n%d\n%d\n",&i,j,0,*j)
为了使实验更具说服力,直接上汇编代码,比较实验的不同点
[plain] view plain copy
-
4: int main() 5: { 00401030 push ebp 00401031 mov ebp,esp 00401033 sub esp,5Ch 00401036 push ebx 00401037 push esi 00401038 push edi 00401039 lea edi,[ebp-5Ch] 0040103C mov ecx,17h 00401041 mov eax,0CCCCCCCCh 00401046 rep stos dword ptr [edi] 6: int i0 = 11; 00401048 mov dword ptr [ebp-4],0Bh 7: 8: const int i=0; 0040104F mov dword ptr [ebp-8],0 //睁大眼睛,编译器确实为常量i分配了栈空间,并赋值为0 9: int *j = (int *) &i; 00401056 lea eax,[ebp-8] 00401059 mov dword ptr [ebp-0Ch],eax 10: *j=1; 0040105C mov ecx,dword ptr [ebp-0Ch] 0040105F mov dword ptr [ecx],1 11: //再看看下面的对比实验,看出对常量的引用和变量的引用的区别 12: const int ck = 9; 00401065 mov dword ptr [ebp-10h],9 //为常量分配栈空间 13: int ik = ck; 0040106C mov dword ptr [ebp-14h],9 //看到否,对常量ck的引用,会直接替换为常量的值9,再看下面的实验 14: 15: int i1 = 5; 00401073 mov dword ptr [ebp-18h],5 16: int i2 = i1; //这里引用变量i1,对i2进行赋值,然后看到否,对常量i1引用没有替换成i1的值,而是去栈中先取出i1的值,到edx寄存器中,然后再把值mov到i2所在的内存中 0040107A mov edx,dword ptr [ebp-18h] 0040107D mov dword ptr [ebp-1Ch],edx 17: 18: 19: return 0; 00401080 xor eax,eax 20: 21: }
通过上述实验的分析可以容易看出,对可折叠的常量的引用会被替换为该常量的值,而对变量的引用就需要访问变量的内存。
总结:常量折叠说的是,在编译阶段,对该变量进行值替换,同时,该常量拥有自己的内存空间,并非像宏定义一样不分配空间,需澄清这点