在C语言中const类型的数据严格意义上可以修改:
const int a=1;
int*b=&a;
*b=2;
不同于C语言,C++中指针类型是要严格对应的,对const类型的数据必须使用const类型的指针进行接收,从而避免修改;
但问题是c++中同样支持指针的强制类型转换那么问题就出现了,对于下面这段代码输出就会显得不正常:
const int a=1;
int*b=(int*)&a;
*b=2;
cout<<a<<" "<<b<<endl;
cout<<&a<<" "<<b;
这个问题其实是编译器的一种优化,编译器会将需要访问的局部const变量在编译阶段存储在寄存器中(存不下的话不优化),我们再对它的地址内数据进行修改时会显得失效,因为使用的是寄存器和地址上的数据已经无关了。
可以查看下列代码的汇编和运行结果证明。
const int t1=1;
struct A{
const int t2=1;
};
void fun(const int&p){
int*q=(int*)&p;
*q=0;
}
void fun(volatile const int&p){
int*q=(int*)&p;
*q=0;
}
int main(){
const int t3=1;
A a;
//fun(t1);报错t1存储在data区只可访问不可修改
fun(a.t2);
fun(t3);
fun(t4);
volatile const int t4=1;
cout<<t1<<a.t2<<t3<<t4;
}
fun(int const&):
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-24], rdi
mov rax, QWORD PTR [rbp-24]
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 0
nop
pop rbp
ret
fun(int const volatile&):
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-24], rdi
mov rax, QWORD PTR [rbp-24]
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], 0
nop
pop rbp
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 1
mov DWORD PTR [rbp-8], 1
mov DWORD PTR [rbp-12], 1
lea rax, [rbp-12]
mov rdi, rax
call fun(int const&)
lea rax, [rbp-4]
mov rdi, rax
call fun(int const&)
lea rax, [rbp-8]
mov rdi, rax
call fun(int const volatile&)
mov esi, 1
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov rdx, rax
mov eax, DWORD PTR [rbp-12]
mov esi, eax
mov rdi, rdx
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, 1
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov rdx, rax
mov eax, DWORD PTR [rbp-8]
mov esi, eax
mov rdi, rdx
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov eax, 0
leave
ret
可以看到cout输出未被volatile修饰的const局部变量时都有:
mov esi, 1
它的作用就是为寄存器赋值,然后接下来对变量的使用会由寄存器代替,volatile会阻止编译期间的优化因而访问时还需访问内存内数据。
volatile的作用是告诉编译器任何时候不对它修饰的成员进行任何优化,打个比方,多线程中一个线程访问完了一个资源,另一个线程也进行了访问,此时缓存如果没有失效第二个读的就是第一个线程的缓存,这样显然是不合理的,缓存失效期间这个资源的状态是未知的,所以可以使用volatile让资源进不去缓存。