常量折叠的理解

转载 2013年12月02日 19:17:41

下面代码的输出为

#include<iostream>
int main(int argc,char** argv){
	const int a=1;
	int* b=const_cast<int*>(&a);
	*b=2;
	std::cout<<&a<<std::endl;
	std::cout<<a<<std::endl;

	std::cout<<b<<std::endl;
	std::cout<<*b<<std::endl;
	return EXIT_SUCCESS;
}

开始很疑惑,经过群里面牛人的指点,知道了是常量折叠的原因,跟编译器有关系,下面是转载的一片关于常量折叠的文章:

转自http://blog.csdn.net/yby4769250/article/details/7359278

可折叠的常量像宏一样,在预编译阶段对常量的引用一律被替换为常量所对应的值,就和普通的宏替换没什么区别,并且,编译器不会为该常量分配空间。现在回顾起来,当时是多么的天真,被现象迷惑了,常量折叠确实会像宏一样把对常量的引用替换为常量对应的值,但是,并非不给该常量分配空间,如下代码:

  1. #define PI 3.14 
  2. int main() 
  3.     const int r = 10; 
  4.  
  5.     int p = pI; //这里会在预编译阶段产生宏替换,PI直接替换为3.14,其实就是int p = 3.14; 
  6.     int len = 2*r; //这里会发生常量折叠,也就是对常量r的引用会替换成他对应的值,相当于int len = 2*10; 
  7.     return 0; 


如上述代码中所述,常量折叠表面上的效果和宏替换是一样的,只是,“效果上是一样的”,而两者真正的区别在于,宏是字符常量,在预编译完宏替换完成后,该宏名字会消失,所有对宏如PI的引用已经全部被替换为它所对应的值,编译器当然没有必要再维护这个符号。而常量折叠发生的情况是,对常量的引用全部替换为该常量如r的值,但是,常量名r并不会消失,编译器会把他放入到符号表中,同时,会为该变量分配空间,栈空间或者全局空间。

为了能更清楚的体现出常量折叠,下面做几个对照实验,看代码和输出便了然:

  1. int main() 
  2.     int i0 = 11; 
  3.  
  4.     const int i=0;         //定义常量i 
  5.     int *j = (int *) &i;   //看到这里能对i进行取值,判断i必然后自己的内存空间 
  6.     *j=1;                  //对j指向的内存进行修改 
  7.         printf("%d\n%d\n%d\n%d\n",&i,j,i,*j); //观看实验效果 
  8.     const int ck = 9;     //这个对照实验是为了观察,对常量ck的引用时,会产生的效果 
  9.     int ik = ck; 
  10.  
  11.     int i1 = 5;           //这个对照实验是为了区别,对常量和变量的引用有什么区别 
  12.     int i2 = i1; 
  13.      
  14.  
  15.     return 0; 
  16.  

上面的代码会输出:

0012ff7c
0012ff7c

0

1

这能说明什么,至少能说明两点:

1、ij地址相同,指向同一块空间,i虽然是可折叠常量,但是,i确实有自己的空间

2、ij指向同一块内存,但是*j = 1对内存进行修改后,按道理来说,*j==1,i也应该等于1,而实验结果确实i实实在在的等于0,这是为什么呢,就是本文所说的内容,i是可折叠常量,在编译阶段对i的引用已经别替换为i的值了,也就是说

  1. printf("%d\n%d\n%d\n%d\n",&i,j,i,*j) 

中的i已经被替换,其实已经被改为

  1. printf("%d\n%d\n%d\n%d\n",&i,j,0,*j) 

为了使实验更具说服力,直接上汇编代码,比较实验的不同点:

  1. 4:    int main() 
  2. 5:    { 
  3. 00401030   push        ebp 
  4. 00401031   mov         ebp,esp 
  5. 00401033   sub         esp,5Ch 
  6. 00401036   push        ebx 
  7. 00401037   push        esi 
  8. 00401038   push        edi 
  9. 00401039   lea         edi,[ebp-5Ch] 
  10. 0040103C   mov         ecx,17h 
  11. 00401041   mov         eax,0CCCCCCCCh 
  12. 00401046   rep stos    dword ptr [edi] 
  13. 6:        int i0 = 11; 
  14. 00401048   mov         dword ptr [ebp-4],0Bh 
  15. 7: 
  16. 8:        const int i=0; 
  17. 0040104F   mov         dword ptr [ebp-8],0 //睁大眼睛,编译器确实为常量i分配了栈空间,并赋值为0 
  18. 9:        int *j = (int *) &i; 
  19. 00401056   lea         eax,[ebp-8] 
  20. 00401059   mov         dword ptr [ebp-0Ch],eax 
  21. 10:       *j=1; 
  22. 0040105C   mov         ecx,dword ptr [ebp-0Ch] 
  23. 0040105F   mov         dword ptr [ecx],1 
  24. 11:                                            //再看看下面的对比实验,看出对常量的引用和变量的引用的区别 
  25. 12:       const int ck = 9; 
  26. 00401065   mov         dword ptr [ebp-10h],9   //为常量分配栈空间  
  27. 13:       int ik = ck; 
  28. 0040106C   mov         dword ptr [ebp-14h],9   //看到否,对常量ck的引用,会直接替换为常量的值9,再看下面的实验 
  29. 14: 
  30. 15:       int i1 = 5; 
  31. 00401073   mov         dword ptr [ebp-18h],5 
  32. 16:       int i2 = i1;                         //这里引用变量i1,对i2进行赋值,然后看到否,对常量i1引用没有替换成i1的值,而是去栈中先取出i1的值,到edx寄存器中,然后再把值mov到i2所在的内存中 
  33. 0040107A   mov         edx,dword ptr [ebp-18h] 
  34. 0040107D   mov         dword ptr [ebp-1Ch],edx 
  35. 17: 
  36. 18: 
  37. 19:       return 0; 
  38. 00401080   xor         eax,eax 
  39. 20: 
  40. 21:   } 


通过上述实验的分析可以容易看出,对可折叠的常量的引用会被替换为该常量的值,而对变量的引用就需要访问变量的内存。


总结:常量折叠说的是,在编译阶段,对该变量进行值替换,同时,该常量拥有自己的内存空间,并非像宏定义一样不分配空间,需澄清这点


 

Android 展开/折叠 系统下拉通知栏

最近几天碰到一个郁闷的问题,在有些机型上面使用PendingIntent.getActivity(context, 0, intent, 0)的方式打开一个指定的Activity后,通知栏并不主动折叠...
  • baiyulinlin1
  • baiyulinlin1
  • 2017年01月03日 11:28
  • 1709

整理:Notepad++使用心得和特色功能介绍

【notepad++简介】 Notepad++是旨在替代Windows默认的notepad而生,比notepad的功能强大很多很多。 Notepad++有两个版本,一个是ANSI版本,一个...
  • bat67
  • bat67
  • 2016年08月02日 09:09
  • 5117

bootstrap(手风琴、图片轮换和固定定位)

手风琴(Collapse) Bootstrap 框架中 Collapse插件(折叠)其实就是我们常见的手风琴效果。点击标题,可以让其对应的内容显示或隐藏 ...
  • qq_20261343
  • qq_20261343
  • 2015年08月05日 21:12
  • 2277

C++高级进阶 第四季:const详解(二) 常量折叠

一、文章来由const详解之二二、const 代替 #defineconst最初动机就是代替 #define。const 优于 #define: (1) #define没有类型检查,const在编译...
  • Scythe666
  • Scythe666
  • 2016年03月23日 00:03
  • 1525

Java常量折叠

常量折叠是Java在编译期间做的一个优化,简单的来说就是在编译期就把一些表达式计算好,不需要在运行时进行计算。对于如下代码:String s1 = "a" + "b"; int a = 1 + 3;j...
  • lgh1992314
  • lgh1992314
  • 2018年01月13日 14:25
  • 26

学习总结:拷贝构造函数、常量折叠、堆\栈

一、关于拷贝构造函数 1、相同类型的类对象是通过拷贝构造函数来完成整个复制过程的; 2、拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量; 3、三种...
  • DjangoBUAA
  • DjangoBUAA
  • 2016年05月09日 14:39
  • 1662

C++const/常量折叠

常量概念:为了使程序员能够在变和不变之间画一条界限,这在C++程序设计中提供了安全性和可控性。 const的最初动机是取代预处理器#define来进行值替代。 宏(#define) 预编译器可以不受限...
  • nie2314550441
  • nie2314550441
  • 2017年05月29日 02:08
  • 208

const_cast引发的常量折叠思考

在学习const_cast转换之后,小小地测试了一下,然后就发现了一个不大不小的问题——论coding的重要性!!!     测试代码如下:     const int i = 5;     ...
  • Ma_D_Shy
  • Ma_D_Shy
  • 2015年07月15日 12:14
  • 375

关于常量折叠

首先来看一个例子: int main(int argc, char* argv[]) { const int i=0; int *j = (int *) &i; *j=1; cout c...
  • q3733353520
  • q3733353520
  • 2014年05月05日 10:28
  • 474

关于常量折叠(转)

首先来看一个例子: int main(int argc, char* argv[]) { const int i=0; int *j = (int *) &i; *j=1; cout c...
  • zssureqh
  • zssureqh
  • 2012年06月18日 15:14
  • 1240
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:常量折叠的理解
举报原因:
原因补充:

(最多只允许输入30个字)