C++中的"volatile"关键字

1.”volatile”的语义

1
volatile int flag = 0;
  • 表示flag可能被硬件、中断、多线程或其他外部因素修改
  • 编译器不能假设其值在两次访问之间保持不变
  • 禁止优化:如删除“看似无用”的读取、合并多次读取、将变量放入寄存器等

2.从汇编角度看

样例代码Code1: (无volatile)
1
2
3
4
5
int flag = 1;
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += flag; // 编译器可能优化为 sum += 1
}

可能生成的汇编代码

1
movl   $1000, %eax    ; sum = 1000 * 1

完全跳过循环和内存访问,因为编译器认为flag不会变。

样例代码Code2: (有volatile)
1
2
3
4
5
volatile int flag = 1;
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += flag; // 必须每次从内存读取 flag
}

可能生成的汇编代码

1
2
3
4
5
6
7
8
9
10
movl   $0, %ebx          ; sum = 0
movl $0, %ecx ; i = 0
.L1:
cmpl $1000, %ecx
jge .L2
movl flag(%rip), %eax ; ← 每次都从内存读取 flag!
addl %eax, %ebx ; sum += flag
incl %ecx
jmp .L1
.L2:
  • 每次循环都执行movl flag(%rip), %eax从内存加载flag
  • 即使flag在循环中未被本程序修改,编译器也不敢假设其值不变

本文章中的汇编不确保可以运行,是”伪代码”

3.样例代码实际输出的汇编码

Compile on Archlinux WSL

样例代码CodeA: (无volatile)
1
2
3
4
5
6
7
8
int main(){
int flag = 1;
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += flag;
}
return 0;
}

汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
	.file	"codeA.cpp"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $1, -4(%rbp)
movl $0, -12(%rbp)
movl $0, -8(%rbp)
jmp .L2
.L3:
movl -4(%rbp), %eax
addl %eax, -12(%rbp)
addl $1, -8(%rbp)
.L2:
cmpl $999, -8(%rbp)
jle .L3
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 15.2.1 20250813"
.section .note.GNU-stack,"",@progbits
样例代码CodeB: (有volatile)
1
2
3
4
5
6
7
8
int main(){
volatile int flag = 1;
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += flag;
}
return 0;
}

汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
	.file	"codeB.cpp"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $1, -12(%rbp)
movl $0, -8(%rbp)
movl $0, -4(%rbp)
jmp .L2
.L3:
movl -12(%rbp), %eax
addl %eax, -8(%rbp)
addl $1, -4(%rbp)
.L2:
cmpl $999, -4(%rbp)
jle .L3
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 15.2.1 20250813"
.section .note.GNU-stack,"",@progbits

为什么没有区别?

因为没有使用编译器优化,O0模式

使用O2

CodeA:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	.file	"codeA.cpp"
.text
.section .text.startup,"ax",@progbits
.p2align 4
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
xorl %eax, %eax
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 15.2.1 20250813"
.section .note.GNU-stack,"",@progbits

CodeB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
	.file	"codeB.cpp"
.text
.section .text.startup,"ax",@progbits
.p2align 4
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
movl $1, -4(%rsp)
movl $1000, %eax
.p2align 4
.p2align 4
.p2align 3
.L2:
movl -4(%rsp), %edx
movl -4(%rsp), %edx
subl $2, %eax
jne .L2
xorl %eax, %eax
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 15.2.1 20250813"
.section .note.GNU-stack,"",@progbits

4.从汇编看

从开启O2的汇编里看,CodeA由于没有使用”volatile”关键字,for循环直接被优化了

1
2
3
main:
xorl %eax, %eax
ret
为什么?
  • flag是编译期常量(值为 1)
  • sum未被使用(无return sum或输出)
  • 编译器判定循环无副作用 -> Dead Code Elimination
    而CodeB中
1
2
3
4
5
6
7
8
9
10
main:
movl $1, -4(%rsp) # flag = 1 存入栈
movl $1000, %eax # 计数器初始化
.L2:
movl -4(%rsp), %edx # <- 强制从内存读取 flag
movl -4(%rsp), %edx # <- 再次读取(可能因对齐或语义保留)
subl $2, %eax
jne .L2
xorl %eax, %eax
ret
  • 循环被保留,尽管flag值不变
  • 每次循环都执行movl -4(%rsp), %edx
  • 即使%edx未被使用,编译器也不敢删除
  • 因为volatile表示“每次访问都有潜在副作用”
  • 循环步长为 2(subl $2, %eax)可能是编译器尝试合并两次加法(sum += flag + flag),但因”volatile”仍需两次独立读取

5.写在最后

  • “volatile”不是运行时特性,而是对编译器优化器的约束。
    -O0(无优化)下,普通变量与”volatile”行为相似(都访问内存),差异不明显
  • -O2等优化级别下,”volatile” 强制保留所有内存访问,确保程序行为符合预期

Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment