正如其他人所说,这些是 CPU 内核本身外部的项目,它可以是 ram,也可以是内存映射的外围设备(可以说是 uart 状态寄存器或定时器寄存器等)。
#define SOME_STATUS_REGA (*((volatile unsigned int *)0x10008000))
void fun ( void )
{
while(SOME_STATUS_REGA==0) continue;
}
#define SOME_STATUS_REGB (*((unsigned int *)0x10008000))
void more_fun ( void )
{
while(SOME_STATUS_REGB==0) continue;
}
只有一个目标和工具链产生
00000000 <fun>:
0: e59f200c ldr r2, [pc, #12] ; 14 <fun+0x14>
4: e5923000 ldr r3, [r2]
8: e3530000 cmp r3, #0
c: 0afffffc beq 4 <fun+0x4>
10: e12fff1e bx lr
14: 10008000 andne r8, r0, r0
00000018 <more_fun>:
18: e59f300c ldr r3, [pc, #12] ; 2c <more_fun+0x14>
1c: e5933000 ldr r3, [r3]
20: e3530000 cmp r3, #0
24: 112fff1e bxne lr
28: eafffffe b 28 <more_fun+0x10>
2c: 10008000 andne r8, r0, r0
您可以看到 more_fun, not volatile case 它读取位置一次并进行一次比较,但进入无限循环。编译器已经完成了我们告诉它做的事情,因为变量无法改变,没有理由烧掉时钟周期重新读取不会改变的东西,所以如果它不是第一个零并且只读取它永远不会为零所以这陷入了一个无限循环。
如果您将其设置为易失性,您就是在“要求”编译器在每次代码访问它时读取或写入它。您可以在有趣的案例中看到,它每次通过循环都会返回以读取该地址以查看它是否已更改。 volatile 关键字是造成这两种行为之间差异的原因。
更改这些值不一定是硬件,如果您使用全局变量在 isr 和前台代码之间进行通信,那么内存中的变量可以由 isr 和/或前台代码更改,因此两者都需要将其视为 volatile。
您还有多核/多线程处理器的情况,其中每个核心/线程独立地可以访问共享资源。在这种情况下,您不仅需要使用 volatile,而且如果内核不共享相同的缓存,您可能需要不缓存该 ram,并且如果需要原子操作(ldrex/ strex 是 ARM 世界的第一步)。
编辑
另一个演示,问题不仅在于读取,还在于写入。假设您有一个外围设备,您需要编写一个配置寄存器来设置某种模式,然后您再次编写它以使用该模式启用它。或者你有一个硬件接口,每次写入都会增加一些逻辑指针,然后你会执行一系列写入来做某事。
#define SOMETHING1 (*((volatile unsigned char *)0x10002000))
void fun ( void )
{
SOMETHING1=5;
SOMETHING1=5;
SOMETHING1=6;
}
#define SOMETHING2 (*((unsigned char *)0x10002000))
void more_fun ( void )
{
SOMETHING2=5;
SOMETHING2=5;
SOMETHING2=6;
}
没有 volatile,该外围设备将无法正常运行。对同一指针/地址的多次写入被视为死代码并进行优化。
00000000 <fun>:
0: e3a02005 mov r2, #5
4: e3a01006 mov r1, #6
8: e59f300c ldr r3, [pc, #12] ; 1c <fun+0x1c>
c: e5c32000 strb r2, [r3]
10: e5c32000 strb r2, [r3]
14: e5c31000 strb r1, [r3]
18: e12fff1e bx lr
1c: 10002000 andne r2, r0, r0
00000020 <more_fun>:
20: e3a02006 mov r2, #6
24: e59f3004 ldr r3, [pc, #4] ; 30 <more_fun+0x10>
28: e5c32000 strb r2, [r3]
2c: e12fff1e bx lr
30: 10002000 andne r2, r0, r0
EDIT2
Clang/llvm 也演示了这个问题
#define A (*((volatile unsigned char *)0x10002000))
void afun ( void )
{
A = 4;
A = 5;
A = 6;
A |= 1;
while(A==0) continue;
}
#define B (*((unsigned char *)0x10002000))
void bfun ( void )
{
B = 4;
B = 5;
B = 6;
B |= 1;
while(B==0) continue;
}
制作
00000000 <afun>:
0: e3a00a02 mov r0, #8192 ; 0x2000
4: e3a01004 mov r1, #4
8: e3800201 orr r0, r0, #268435456 ; 0x10000000
c: e5c01000 strb r1, [r0]
10: e3a01005 mov r1, #5
14: e5c01000 strb r1, [r0]
18: e3a01006 mov r1, #6
1c: e5c01000 strb r1, [r0]
20: e5d01000 ldrb r1, [r0]
24: e3811001 orr r1, r1, #1
28: e5c01000 strb r1, [r0]
2c: e5d01000 ldrb r1, [r0]
30: e3510000 cmp r1, #0
34: 0afffffc beq 2c <afun+0x2c>
38: e12fff1e bx lr
0000003c <bfun>:
3c: e3a00a02 mov r0, #8192 ; 0x2000
40: e3a01007 mov r1, #7
44: e3800201 orr r0, r0, #268435456 ; 0x10000000
48: e5c01000 strb r1, [r0]
4c: e12fff1e bx lr
如果您正在做的事情不在可以优化它们的域中,那么添加 volatile 不会对您造成伤害。 (以某种顺序对每个寄存器进行单次写入,对寄存器进行单次读取,单次也意味着没有循环)。如果您进行多次写入(通常在配置外围设备时发生)进行读取修改写入(x |= 某物,y &= 某物,z ^= 某物等),那肯定会伤害您。
如果您使用的工具链没有优化器,或者您选择不优化,则不会出现此问题,但如果您关闭 volatile,则该代码不可移植,如果您不习惯处理,最终会遇到麻烦具有跨编译或其他类似域的变量/代码(硬件是独立于软件的编译域)。