C++的常量折叠(三)
背景知识
在开始之前先说一下符号表,这个编译器中的东西。下面看一下百度百科中的描述:
符号表是一种用于语言翻译器中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。
符号表在编译程序工作的过程中不断收集、记录和使用源程序中一些语法符号的类型和特征等相关信息。这些信息一般以表格形式存储于系统中。如常数表、变量名表、数组名表、过程名表、标号表等等,统称为符号表。对于符号表组织、构造和管理方法的好坏会直接影响编译系统的运行效率。
还有一个问题:前面说的似乎很让人烦,既然声明成了const,干嘛还老修改啊?
根据C++标准,对于修改const变量,属于:未定义行为(指行为不可预测的计算机代码),这样一来此行为取决于各种编译器的具体实现(即不同编译器可能表现不同)。
看到了吧,C++标准是不提倡这么玩的。
下面看一下前面说的那个对照程序:
int main() { int i0 = 11; const int i = 0; //定义常量i int *j = (int *) &i; //看到这里能对i进行取值,判断i必然后自己的内存空间 *j = 1; //对j指向的内存进行修改 printf("0x%p\n0x%p\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; }
同时把i的声明那条语句改成:
const volatile int i = 0; //定义常量i
我们知道volatile是告诉编译器在翻译源码到汇编语言(机器码)的过程中,不要优化。把源码中对i的访问,翻译成每次都要去内存中抓取数据(这里是在做编译工作,只是把对应的源码翻译成汇编语言),而不是从符号表(常量表)中抓取数据。所以加上volatile关键字的i的访问都是去内存中拿数据,不去常量表中。
现在还有个问题,如果我加上volatile关键字,那么每次对i的访问的语句都被翻译成了”去看内存里的数据“这种行为的汇编语言,不会有常量替换的过程了,那么下面的语句是不是合法了呢?
i = 10;
你修改了常量的值,怎么可能合法呢?但是按照上面的说法对于const volatile int类型的i似乎又是合法的。问题出在了这里:
编译器的一部分工作流程是这样的:语法检测->预编译(宏替换,常量替换,*(&i)恒等于i等优化)->编译。所以i = 10这句话在语法检查这个阶段,看到你对一个const常量赋值,就已经报错了,根本到不了编译这个阶段。
但是有的一些编译器似乎无视这个volatile关键字,下面看一下测试的结果:
(1)看vc6.0的结果:
没有volatile关键字的:
有volatile关键字的:
即在VC++6.0编译环境下,在const变量定义时添加volatile修饰符,与不添加效果是一样的。编译器都采取了优化(甚至把编译器优化选项关闭还是如此,无奈了)。
(2)vs2010和g++的测试结果是一样的:
没有volatile关键字:
有volatile关键字:
所以:不建议修改const变量的值,即使修改也要熟悉当前使用的编译器对于该 未定义行为 是如何解释的。