常量折叠(转载)

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

今天回顾了大学这几年学习C++的点点滴滴,在回顾了“常量折叠”这里时,突然困惑了,当时学习这点知识时的理解是:可折叠的常量像宏一样,在预编译阶段对常量的引用一律被替换为常量所对应的值,就和普通的宏替换没什么区别,并且,编译器不会为该常量分配空间。现在回顾起来,当时是多么的天真,被现象迷惑了,常量折叠确实会像宏一样把对常量的引用替换为常量对应的值,但是,并非不给该常量分配空间,如下代码

  1. #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的值了,也就是说

  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)  

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

[plain] view plain copy

  1. 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:   }  

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

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

时间: 2024-10-24 00:40:33

常量折叠(转载)的相关文章

const常量,常量折叠,字面常量

const int a=10: 涉及到一个叫常量折叠的概念(认为我这说得太简单或者不好理解的可以google一下它获取更多信息), 即编译器虽然会给a分配空间(如果取a的地址进行操作的时候,会强迫编译器进行内存分配), 但是在预编译阶段, 会把所有的a用10替换(这就有点像#define了), 所以虽然&a地址存放的内容改变了, 但是a依然为10. 10是字面常量 编译器实现的时候,可以优化,对int类型的字面常量,直接用立即数代替,这样10就不分配内存空间,但是对于“hello”这样的字符常量

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

一.文章来由 const具体解释之二 二.const 取代 #define const最初动机就是取代 #define. const 优于 #define: (1) #define没有类型检查,const在编译期(而不是预编译期)做类型检查. (2)const方便调试和定位bug. 所以应该全然用const取代#define 三.头文件里的const (1)要使用const取代#define.相同须要把const定义放进头文件(或其它格式文件,include就可以). 这样通过包括头文件.可把c

C++的常量折叠(一)

前言 前几天女票问了我一个阿里的面试题,是有关C++语言的const常量的,其实她一提出来我就知道考察的点了:肯定是const常量的内存不是分配在read-only的存储区的,const常量的内存分配区是很普通的栈或者全局区域.也就是说const常量只是编译器在编译的时候做检查,根本不存在什么read-only的区域. 所以说C++的const常量和常量字符串是不同的,常量字符串是存储在read-only的区域的,他们的具体的存储区域是不同的. 就好像杨立翔老师在上课举得那个例子(讲的东西忘得差

C++的常量折叠(二)

前面的C++的常量折叠(一)的最后留下了一个问题,那就是在声明i的时候,加上修饰符volatile关键字,发现输出的就不一样了,下面来说一下volatile这个关键字. C/C++中的volatile关键字和const对应,用来修饰变量,通常用于建立语言级别的memory barrier.下面这句话是Stroustrup在"The C++ Programming Language"中对volatile修饰词的说明: A volatile specifier is a hint to a

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

一.关于拷贝构造函数 1.相同类型的类对象是通过拷贝构造函数来完成整个复制过程的: 2.拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量: 3.三种情况下会调用拷贝构造函数:对象以值传递的方式传入函数参数.对象以值传递的方式从函数返回.对象需要通过另外一个对象进行初始化: 相关博文: http://blog.csdn.net/lwbeyond/article/details/6202256 二.常量折叠 1.编译器进行语法分析的时候,将常量表达

C#与Java对比学习:类型判断、类与接口继承、代码规范与编码习惯、常量定义(转载)

C#与Java对比学习:类型判断.类与接口继承.代码规范与编码习惯.常量定义 类型判断符号: C#:object a;  if(a is int) { }  用 is 符号判断 Java:object a; if(a instanceof Integer) { } 用 instanceof 符号判断 类与接口的继承: C#:public class MDataRow : List<MDataCell>, IDataRecord, ICustomTypeDescriptor Java:publi

C++的常量折叠--const和volatile

背景知识 在开始之前先说一下符号表,这个编译器中的东西.下面看一下百度百科中的描述: 符号表是一种用于语言翻译器中的数据结构.在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型.作用域以及内存地址.符号表在编译程序工作的过程中不断收集.记录和使用源程序中一些语法符号的类型和特征等相关信息.这些信息一般以表格形式存储于系统中.如常数表.变量名表.数组名表.过程名表.标号表等等,统称为符号表.对于符号表组织.构造和管理方法的好坏会直接影响编译系统的运行效率. 还有一

【转载】编译时与运行时

在开发和设计的时候,我们需要考虑编译时,运行时以及构建时这三个概念.理解这几个概念可以更好地帮助你去了解一些基本的原理.下面是初学者晋级中级水平需要知道的一些问题. Q.下面的代码片段中,行A和行B所标识的代码有什么区别呢? public class ConstantFolding { static final int number1 = 5; static final int number2 = 6; static int number3 = 5; static int number4= 6;

C++中Const说明

最近在看程序员面试宝典,看到const这块感觉有很大疑惑,查了很多资料,可以总结如下: 1.在C语言中 在C语言中,const修饰的变量不具有常量的特性,只是一个不可修改的变量,实质上仍然是变量,在编译期间无法知道它的值,不可以用作数组下标. 2.在C++中 在C++中,const就有很大不一样,C++中鼓励使用const来替代#define,在C++中对const定义的变量分为两种情况: 情况1(在.rodata段分配空间): 如果const用在全局或者使用了static关键字说明,例如ext