C/C++ const和volatile

    前两天朋友叫我帮忙做几道题(阿里的笔试题?),遇到这样一道题:
        const int a = 10;
	int *p = (int*)(&a);
	
	*p = 20;
	
	cout << &a << " " << a << " " << *p << " "  << p << endl;
 
    乍一看,似乎很明显,const关键字表示常量,不允许修改,那就应该会出现编译错误喽?哪有这么简单的事!我运行了一下,发现竟然可以正常编译运行,但是结果是a=10,*p=20.看起来有些不可思议,难道为了避免const变量的值被修改,将a的地址赋值给指针p时又重新分配了一个int型变量的空间?但是,用C语言改写一下这段代码:
        const int a = 10;
	int *p = (int*)(&a);
	
	*p = 20;
	
	printf("%d %d", a, *p);


        发现运行结果为a=20,*p=20.为什么?const变量被改变了!其实在C/C++语言里,const的目的(似乎)并不是为了防止变量被修改,而是为了防止变量被“无意间”修改,这个关键字并没有我之前以为的能为变量加上访问权限的功能,只需要像以上代码做显示类型转换或者使用const_cast或者mutable关键字,就可以修改const变量。在Dev-C++上,甚至可以不用做显示类型转换,

           const int a = 10;
	int *p = &a;
	
	*p = 30;
 

这段代码是可以正确编译运行的,编译器仅仅是给出警告而已:

[Warning] initialization discards 'const' qualifier from pointer target type [enabled by default]

        在Stroustrup的《C++程序设计语言》里有关于const的内容,const更多地是用来替代C语言中的宏即#define的作用,因为宏是由预处理器(preprocessor)在编译之前开展的,不属于编译器职责范围,因此相关的类型检查等语言本身的安全机制都无法发挥作用;同时,宏的展开常常是简单的文本替换,在上下文比较复杂的情况下,无法预料一段宏展开后会与上下文结合产生什么样的代码,例如:

#define add(x,y) x+y

int a = 2*add(1, 3);

        我想大部分人是知道a=5的。这个宏并不能按照程序员的本意工作,预处理过程后,代码实际上是

int a = 2*1+3;

        add(1, 3)被文本替换为1+3,而放到程序中,因为运算符的优先级问题,这个宏并不能按照我们当初预想的那样工作。解决方法通常是加上括号,然而这只是一个很简单的例子,在更复杂的例子里,需要更为复杂的处理,例如放在do{}while(0)语句里,经验不足的程序员很难写出精炼而能正确工作的宏;更糟糕的是,宏为代码加上了一层间接性,我们在定义宏时很难预见宏会在何时何地被展开,与上下文发生何种作用,调试代码时如果仅仅观察源代码而不查看预处理后的代码,常常很难发现某些隐晦的错误,这就使调试的难度加大了。宏是独立于编译系统之外的,享受不到编译器带给我们的种种便利,事实上,现代编译器对源代码做了许多工作,游荡在编译器的职责范围外是很危险的。详细内容仍然可以参阅《C++程序设计语言》等各种著作。

       好了,到目前为止,C的部分说完了,但是第一个C++程序仍然没有解释,为什么a的值保持不变,难道C++关于const的规定和C语言不同,即使进行显示类型转换,也不能改变const变量的值?非也。变量a的值其实改变了,但编译器在这里做了一个优化:常量折叠。编译器检测到变量a被声明为常量,认为a的值保持不变,因此每次调用a的值时直接用常量取代变量a,而不从内存中读取a的值,所以int b = a;被编译器优化为int b = 10;这样可以节省一次读取内存的操作。怎么证明呢?可以在变量前加上volatile关键词,禁止编译器对此变量进行优化,结果就对了。可以查看汇编代码:

未加volatile关键词

mov    DWORD PTR [ebp-0xc],0xa
mov    DWORD PTR [esp],0xa

直接用常量0xa赋值

加volatile

mov    DWORD PTR [ebp-0xc],0xf
mov    eax,DWORD PTR [ebp-0xc]
mov    DWORD PTR [esp],eax

就要从相应的地址取变量的值了。(其实我汇编学得本来就渣,又好久没碰了,这些代码具体是啥意思我也不甚清楚,瞎蒙的,呵呵~:)

        还有一个问题,在C++中,如果用volatile修饰变量,再输出这个变量的地址,结果为1.为什么呢?根据网上的信息,ostream的operator<< 并没有重载支持volatile修饰的变量,因此cout << 一个volatile变量,实际上是调用以bool类型为参数的操作符,也就是说,volatile变量的地址被作为一个bool类型,一般地址都是非0的,非0值的bool值当然就是1了。

       好了,这篇文章就差不多写完了,本来标题可以取得再吸引眼球一点,什么“阿里笔试题”,呵呵。

       这几年没怎么写过博客,在CSDN论坛发帖的次数也不多。既不懂得利用网络资源,也静不下心来踏踏实实地学点东西,说到底,还是太懒散了啊。希望以后能有点长进,欢迎指点~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值