0%

volatile在汇编层面的分析

今天正好在群里跟朋友聊volatile的问题,测了一下gcc对volatile变量和正常变量的实现区别,顺便记一下。

备注:此volatile是在c语言在gcc下的测试结果和原理,和java里的volatile完全两个概念,不要混淆!不要混淆!不要混淆!

首先写一个很简单的demo:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int a = 100;

void foo() {
while (a > 10) {}
}

int main() {
foo();
return 0;
}

重点就是看反编译后的foo函数的实现,循环判断a是否大于10,然后看a是普通变量和volatile修饰变量的差异:

普通变量的gcc编译后的汇编代码如下:

1
2
3
4
5
6
7
8
0000000000000620 <foo>:
620: 8b 05 ea 09 20 00 mov eax,DWORD PTR [rip+0x2009ea] # 将a的值从内存加载至寄存器
626: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
62d: 00 00 00
630: 83 f8 0a cmp eax,0xa # 将eax寄存器的值与0xa(10)做对比
633: 7f fb jg 630 <foo+0x10> # jg,如果大于目标值,跳转至630,继续对比eax和0xa
635: f3 c3 repz ret
637: 66 0f 1f 84 00 00 00 nop WORD PTR [rax+rax*1+0x0]

对变量a加上volatile修饰后的汇编代码如下:

1
2
3
4
5
6
0000000000000610 <foo>:
610: 8b 05 fa 09 20 00 mov eax,DWORD PTR [rip+0x2009fa] # 将a的值从内存加载至寄存器
616: 83 f8 0a cmp eax,0xa # 将eax寄存器的值与0xa(10)做对比
619: 7f f5 jg 610 <foo> # jg,如果大于目标值,跳转至610,重新将a的值从内存加载至eax
61b: f3 c3 repz ret
61d: 0f 1f 00 nop DWORD PTR [rax]

可以明显的看到,未加volatile的汇编是每次只跟eax寄存器中的值做对比,而加上volatile之后,是跳转至610,也就是会重新将a的值从内存加载至寄存器,保证每次a的值都是最新,从而保证了“可见性”