一、register关键字修饰变量
首先register是关键字,关键字意味着这样的变量是由编译器处理的。他的作用就是尽量让这个被修饰的变量存放在CPU的寄存器中供程序进行读写,因为他的值很少被修改,直接通过寄存器访问,就能提高程序的性能。因为对变量的访问是直接访问的寄存器,而不是比寄存器还要慢的内存。但是这里有几个点需要注意:
1、一个变量被修饰为一个register变量,意味着这个变量是频繁变化和访的。意味着他的生命周期很短暂,但是又要频繁访问。这个是register变量的特点。如果register去修饰一个全局变量的话,因为全局变量生命周期是整个程序作用阈的,也就是程序完成了生命周期才结束。那么就要让这个全局变量长期或者一直占据某个CPU寄存器,这个显然违背计算机设计且不合理。因此register变量只能修饰局部变量和形参(函数定义时候的参数)。
2、因为一般CPU的寄存器个数只有10多个(比如16个),因此如果要想定义成register的的所有变量都一定放在CPU寄存器中,显然是不太可能,而这个谁来决定,取决于编译这个程序的编译器。编译器会根据当前CPU寄存器使用情况来决定那些变量可以放在寄存器中进行处理。所以即便register定义了变量,不代表这个变量一定放在寄存器中进行处理。如果没有放在寄存器中处理,则这个变量和普通的临时变量没有区别,还是放在内存中进行处理。
3、因为寄存器变量是放在寄存器中处理的,而不是放在内存中处理的,所以对寄存器变量取地址显然是不合理的,因为寄存器压根就没有地址一说,地址都是对内存而言的。因此对一寄存器变量取地址就是错误的。gcc编译的时候会报错。
但是g++可以正确处理这个编译,是可以编译通过的,而且能把相应的地址取出来。
4、同时寄存器一般都是32位的,所以不能把一个64位的变量定义为一个寄存器变量,因为这个变量根本放不下在一个寄存器中。----但是现在的CPU貌似都可以在gcc和g++编译器下编译通过,且可以正常运行。
二、volatile关键字修饰变量
和register修饰变量正好相反,这个关键字是告诉编译器这个变量是经常会变的,所谓的变化是指的他的值在内存中不停的变化。那么对这个值的访问就不能只是在CPU寄存器中访问了,则需要到外面内存中去取最新的值来访问保证数据一致性。他修饰变量时候主要是告诉编译起不要进行代码优化,而且读取这个变量的值要去内存中读取,而不是从register中读取。举个例子:
int a ;
a = 100;
a = 200;
a = 300;
a = 400;
如果是在gcc -O3优化设置下,上述四行代码只会被优化位一行机器命令,也就是只有a=400那条指令会被计算机处理。如果我们1,2,3,4步骤是要去做一件事件,那么效果就是只会做4步的事情。而1,2,3步都不会做,那么如果我们把int a 变成volatile int a;则就会告诉编译起这个变量每次都从内存中获取数据,这样其实每一步都会执行。其实在软中断中,因为共享的变量在中断前被赋值,然后中断后进行处理的话,有可能由于代码优化就会使中断处理的代码中使用到不符合预期的变量值,导致错误。其实这个在实际的工作中遇到过,此时需要对这个变量进行volatile修饰。这样编译器就不会对中断开始和中断后的代码进行优化了。程序执行就会严格按照代码逻辑进行。
还有一种场景就是多线程模型下,其他线程对变量的更改发生在内存中,而某个线程由于频发访问这个变量,导致该线程的CPU把这个变量放在了寄存器中,因此如果其他线程修改了这个变量在内存的值的话,那么该线程就还是读取到的旧的值。这样数据就不一致了。
其实解决中这样的问题的办法还有一个办法就是通过内存屏障的办法解决,本质也是在中断前和告诉编译器不能优化中断前和中断后的代码逻辑顺序,此时程序就会严格按照中断的逻辑对代码进行处理,同时对其后面的变量访问也会从内存中进行获取。从而保证数据的一致性。这里内存屏障其实就是用了一个volatile修饰;
#define set_mb(var, value) do { var = value; mb(); } while (0)
#define mb() __asm__ __volatile__ ("" : : : "memory")
- __asm__用于指示编译器在此插入汇编语句
- volatile用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。
- memory 强制 gcc 编译器假设 RAM 所有内存单元均被汇编指令修改,这样 cpu 中的 registers 和 cache中已缓存的内存单元中的数据将作废。cpu 将不得不在需要的时候重新读取内存中的数据。这就阻止了 cpu 又将 registers,cache 中的数据用于去优化指令,而避免去访问内存。
- “”::: 表示这是个空指令。barrier()不用在此插入一条串行化汇编指令。在后文将讨论什么叫串行化指令。