c/c++面试常见问题(三)
C/C++面试常见问题归纳(三)
volatile关键字
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
声明时语法:int volatile vInt;
当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
例如:
volatile int i=10;
a = i;
b = i;
volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。
而优化的做法是:
由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。
这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。
注意
- 除了基本类型外,对用户定义类型也可以用volatile类型进行修饰。
- C++中一个有volatile标识符的类只能访问它接口的子集,一个由类的实现者控制的子集。用户只能用const_cast来获得对类型接口的完全访问。此外,volatile向const一样会从类传递到它的成员。
多线程下的volatile
有些变量是用volatile关键字声明的。当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。
- 这个关键字是用来设定某个对象的存储位置在内存中,而不是寄存器中。因为一般的对象编译器可能会将其的拷贝放在寄存器中用以加快指令的执行速度。
- volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值》
const_cast关键字
-
去掉const属性:const_cast<int*> (&num),常用,因为不能把一个const变量直接赋给一个非const变量,必须要转换。
-
加上const属性:const int* k = const_cast<const int*>(j),一般很少用,因为可以把一个非const变量直接赋给一个const变量,比如:const int* k = j;
const_cast转换符是用来移除变量的const或volatile限定符。
对于const修饰的变量,一般值是无法改变的。但是如我我们想要违背const的意愿要修改变量的值呢?
在C语言中可以用强制类型转换来实现
const int constant = 21;
int* modifier = (int*)(&constant);
下面代码实现将const int * 型的变量转换为int * 型
int main()
{
const int constant = 20;
const int* const_p = &constant;
int* modifier = const_cast<int*> (const_p);
*modifier = 7;
cout << "constant: " << constant << endl;
cout << "*const_p: " << *const_p << endl;
cout << "*modifier: " << *modifier << endl;
cout << "&constant: " << &constant << endl;
cout << "const_p: " << const_p << endl;
cout << "modifier: " << modifier << endl;
return 0;
}
执行结果如下:
constant: 20
*const_p: 7
*modifier: 7
&constant: 0x6dfee4
const_p: 0x6dfee4
modifier: 0x6dfee4
根据执行结果我们可以看到,constant的值并没有改变,而modifier最终也指向了constant的地址。
那为什么相同的地址会出现两个完全不一样的值???
IBM的C++指南称呼“*modifier = 7;”为“未定义行为(Undefined Behavior)”。所谓未定义,是说这个语句在标准C++中没有明确的规定,由编译器来决定如何处理。
这就是C++中的常量折叠:指const变量(即常量)值放在编译器的符号表中,计算时编译器直接从表中取值,省去了访问内存的时间,从而达到了优化。
换句话说,这种行为在不同的编译器下执行的结果可能会有差异。
const_cast使用场景
- 可能调用了一个参数不是const的函数,而我们要传进去的实际参数确实const的,但是我们知道这个函数是不会对参数做修改的。于是我们就需要使用const_cast去除const限定,以便函数能够接受这个实际参数。
- 定义了一个非const的变量,但用带const限定的指针去指向它,在某一处我们突然又想修改了,可是我们手上只有指针,这时候我们可以去const来修改了。
int main()
{
int var = 3;
const int* c_p = & var;
var = 5;
// *c_p = 5; // 不能赋值
int* mod = const_cast<int*> (c_p);
*mod = 5;
cout << var << endl;
cout << *c_p << endl;
return 0;
}
执行结果如下:
var: 5
*c_p: 5
const关键字
const关键字用于修饰常量,一般情况下不能修改。如果要想修改怎么办?从前面的验证可知使用const_cast对const变量修改是不可靠的。唯一可能实现的就是使用volatile关键字:
int main()
{
const volatile int constant = 20; // 添加volatile关键字,禁止编译器的优化,直接从地址中读取该数
const volatile int* const_p = &constant;
int* modifier = const_cast<int*> (const_p);
*modifier = 7;
cout << "constant: " << constant << endl;
cout << "*const_p: " << *const_p << endl;
cout << "*modifier: " << *modifier << endl;
cout << "&constant: " << &constant << endl;
cout << "const_p: " << const_p << endl;
cout << "modifier: " << modifier << endl;
return 0;
}
输出结果如下
constant: 7
*const_p: 7
*modifier: 7
&constant: 1
const_p: 1
modifier: 0x6dfedc
可以看到,constant的值被修改成7。但是volatile关键字并不是所有的编译器都支持,比如VC++6.0。也就是说在VC++6.0下,即使使用volatile关键字还会进行优化,输出的constant的值还会是20。