了解“ volatile”关键字和比较的工作方式

snr*_*snr 0 c assembly x86-64 volatile

如果未使用关键字指定变量volatile,则编译器可能会进行缓存。必须始终从内存访问该变量,否则直到其事务单元结束。我想知道的重点在于装配零件。

int main() {
    /* volatile */ int lock = 999;
    while (lock);
}
Run Code Online (Sandbox Code Playgroud)

x86-64-clang-3.0.0编译器上,其汇编代码如下。

main:                                   # @main
        mov     DWORD PTR [RSP - 4], 0
        mov     DWORD PTR [RSP - 8], 999


.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        cmp     DWORD PTR [RSP - 8], 0
        je      .LBB0_3
        jmp     .LBB0_1


.LBB0_3:
        mov     EAX, DWORD PTR [RSP - 4]
        ret
Run Code Online (Sandbox Code Playgroud)

volatile关键字被注释时,结果如下。

main:                                   # @main
        mov     DWORD PTR [RSP - 4], 0
        mov     DWORD PTR [RSP - 8], 999


.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        mov     EAX, DWORD PTR [RSP - 8]
        cmp     EAX, 0
        je      .LBB0_3
        jmp     .LBB0_1


.LBB0_3:
        mov     EAX, DWORD PTR [RSP - 4]
        ret
Run Code Online (Sandbox Code Playgroud)

我想知道和不理解的要点

  • cmp DWORD PTR [RSP - 8], 0。<---为什么比较完成的0,同时DWORD PTR [RSP - 8]拥有999内?
  • 为什么要DWORD PTR [RSP - 8]复制到中EAX,为什么还要在0和之间进行比较EAX

Pet*_*des 5

您好像忘记了启用优化。 -O0对待所有变量(变量除外register)的方式与volatile一致性调试非常相似

启用优化后,编译器可以将非易失性负载提升到循环之外。while(locked);将像源一样编译

if (locked) {
    while(1){}
}
Run Code Online (Sandbox Code Playgroud)

或由于locked具有编译时常数初始化器,因此整个函数应编译为jmp main(无限循环)。

有关更多详细信息,请参见MCU编程-C ++ O2优化在循环时中断


为什么要DWORD PTR [RSP - 8]复制到EAX中,又为什么要在0和EAX之间进行比较?

当您使用时,某些编译器在将负载折叠到其他指令的内存操作数中时效果更差volatile。我认为这就是为什么您在mov这里得到单独的负担。这只是一个错过的优化。

(尽管效率cmp [mem], imm可能较低。我忘记了它是否可以与JCC或其他宏宏融合。在具有RIP相对寻址模式的情况下,它无法对融合负载进行微融合,但是寄存器基可以。)


cmp EAX, 0很奇怪,我猜禁用优化的clang不会test eax,eax作为比较零的窥视孔优化。

正如@ user3386109所评论的,locked在布尔上下文中等效locked != 0于C / C ++。