非volatile全局变量在含函数调用的循环中为何每次从内存加载?
问题:循环内函数调用导致非volatile全局变量每次从内存加载的原因
原始C代码
#include <stdio.h> #include <stdbool.h> bool flag; static void foo(int a, int b) { printf("why\n"); return; } int main() { while (!flag) { foo(10, 11); } return 0; }
编译与反汇编命令
使用以下命令编译代码:
aarch64-linux-gnu-gcc -O2 t.c
再通过以下命令生成反汇编代码:
aarch64-linux-gnu-objdump -Sdf a.out > t
反汇编结果(main函数部分)
55 0000000000400460 <main>: 56 400460: a9be7bfd stp x29, x30, [sp, #-32]! 57 400464: 910003fd mov x29, sp 58 400468: f9000bf3 str x19, [sp, #16] 59 40046c: b0000093 adrp x19, 411000 <__libc_start_main@GLIBC_2.17> 60 400470: 3940c660 ldrb w0, [x19, #49] 61 400474: 35000140 cbnz w0, 40049c <main+0x3c> 62 400478: f9000fb4 str x20, [x29, #24] 63 40047c: 9100c673 add x19, x19, #0x31 64 400480: 90000014 adrp x20, 400000 <_init-0x3e8> 65 400484: 91198294 add x20, x20, #0x660 66 400488: aa1403e0 mov x0, x20 67 40048c: 97fffff1 bl 400450 <puts@plt> 68 400490: 39400260 ldrb w0, [x19] 69 400494: 34ffffa0 cbz w0, 400488 <main+0x28> 70 400498: f9400fb4 ldr x20, [x29, #24] 71 40049c: 52800000 mov w0, #0x0 // #0 72 4004a0: f9400bf3 ldr x19, [sp, #16] 73 4004a4: a8c27bfd ldp x29, x30, [sp], #32 74 4004a8: d65f03c0 ret
疑问
为何第68行总是从内存加载flag?它并非volatile类型,按预期应该仅从内存加载一次后从寄存器读取。若移除C代码第16行(循环内无函数调用),则只会从内存加载一次flag。似乎是循环中的函数调用导致了这种现象,恳请对此作出解释。
解释
这是编译器优化的保守性规则导致的,核心原因如下:
- 全局变量的不确定性:
flag是全局变量,作用域覆盖整个程序。编译器在做-O2级别的优化时,无法确定被调用的foo函数是否会修改这个全局变量——哪怕foo的代码里看起来没有修改flag,但编译器不会默认排除这种可能(比如foo可能通过指针间接修改flag,或者后续代码变更会影响当前编译单元)。 - 正确性优先的优化策略:为了保证程序行为的正确性,编译器会保守地假设任何函数调用都可能修改全局变量。因此每次从
foo返回后,必须重新从内存读取flag的值,而不能依赖之前存在寄存器里的副本——如果不这么做,一旦foo真的修改了flag,循环就无法感知到变化,导致逻辑错误。 - 无函数调用时的优化:当循环内没有函数调用时,编译器能明确看到这段代码里没有任何修改
flag的操作,因此可以安全地将flag的值加载到寄存器一次,之后循环里直接用寄存器的值判断,这是符合优化规则的。
内容的提问来源于stack exchange,提问作者Leslie Li
相关产品推荐
相关产品推荐

