C++苦字符串格式化很久了,终于在C++20标准中提供了std::format方法,于是就在makefile里面加上了-std=c++20的flag,编译其它地方都没问题,但是报了一个error,使用到的一个三方库里面有个头文件报increment of object of volatile-qualified type 'volatile int' is deprecated , 于是仔细研究了一下这个volatile关键字。
volatile作为关键字的定义很明确,volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。但是我们怎么知道编译器什么时候会去假设这个变量的值呢?于是用godbot做了一些简单的实验,选择的是我自己使用的编译器x86-64 clang 15.0.0
首先看下面这段简单的code
int num = 10;
int square() {
int b = num;
int a = num;
return b * a;
}
编译后的汇编代码如下:
square(): # @square()
push rbp
mov rbp, rsp
mov eax, dword ptr [rip + num]
mov dword ptr [rbp - 4], eax
mov eax, dword ptr [rip + num]
mov dword ptr [rbp - 8], eax
mov eax, dword ptr [rbp - 4]
imul eax, dword ptr [rbp - 8]
pop rbp
ret
num:
.long 10 # 0xa
这是完全没有经过优化,首先把num对应的内存中的内容move到eax寄存器中,然后把寄存器内容move到b对应的内存中,对a也重复上面的过程,最后把b对应的内存中的内容移动到寄存器,然后用寄存器中的内容和a对应的内存中的内容做乘法。
打开O3编译优化之后的汇编代码如下:
square(): # @square()
mov eax, dword ptr [rip + num]
imul eax, eax
ret
num:
.long 10 # 0xa
直接把num对应的内存中的内容move到寄存器,然后把寄存器里面的内容相乘,编译器假设了变量a和b从num对应的内存中取出来的值是一样的,从而省略了中间的b = num和a = num两条C语句。
修改一下code,在num的定义前面加上volatile之后的汇编代码如下
square(): # @square()
mov eax, dword ptr [rip + num]
imul eax, dword ptr [rip + num]
ret
num:
.long 10 # 0xa
区别不大,就是最后乘的时候用的是寄存器中的值和内存中的值来乘,而不是没有volatile的时候寄存器和寄存器相乘,这样能保证乘的是最新的值,与未优化的两条C语句等价。
看到这里我们应该明白,想要编译器对非volatile的变量做假设优化并不是很容易,需要这个变量已经在寄存器里面了,并且很快又会被用到,大多数程序很难满足这个要求,除非故意在一段code里面等待外部来改变这个变量。
回头再去看我的项目里面报错的那个第三方头文件,那个变量在所有使用的地方都不可能做假设优化,也就是可以很放心的把volatile关键字删掉。