GCC针对ARM Cortex-M4优化全局变量的规则咨询
GCC针对ARM Cortex-M4全局变量的优化规则解析
问题背景
使用gcc-arm-none-eabi-8-2019-q3-update/bin/arm-none-eabi-gcc编译ARM Cortex-M4平台代码,优化等级为-Os时,发现同一全局变量在不同函数中的优化行为存在差异:
foo1()中全局变量仅被加载一次,导致循环无法退出foo2()中每次循环都会重新加载全局变量,循环可正常退出
已知添加volatile关键字可避免此优化,现解析背后的GCC优化规则。
测试代码
foo.c
int g_global_cnt = 0; void dummy_func(void); void global_cnt_add(void) { g_global_cnt++; } int foo1(void) { while (g_global_cnt == 0) { // do nothing } return 0; } int foo2(void) { while (g_global_cnt == 0) { dummy_func(); } return 0; }
bar.c
void dummy_func(void) { // do nothing }
注:foo1()、foo2()在任务A中调用,global_cnt_add()在任务B中调用。
汇编代码对比
foo1的汇编
int foo1(void) { while (g_global_cnt == 0) { 201218: 4b02 ldr r3, [pc, #8] ; (201224 <foo1+0xc>) 20121a: 681b ldr r3, [r3, #0] 20121c: b903 cbnz r3, 201220 <foo1+0x8> 20121e: e7fe b.n 20121e <foo1+0x6> // do nothing } return 0; } 201220: 2000 movs r0, #0 201222: 4770 bx lr 201224: 00204290 .word 0x00204290
foo2的汇编
int foo2(void) { 201228: b510 push {r4, lr} while (g_global_cnt == 0) { 20122a: 4c04 ldr r4, [pc, #16] ; (20123c <foo2+0x14>) 20122c: 6823 ldr r3, [r4, #0] 20122e: b10b cbz r3, 201234 <foo2+0xc> dummy_func(); } return 0; } 201230: 2000 movs r0, #0 201232: bd10 pop {r4, pc} dummy_func(); 201234: f1ff fcb8 bl 400ba8 <dummy_func> 201238: e7f8 b.n 20122c <foo2+0x4> 20123a: bf00 nop 20123c: 00204290 .word 0x00204290
优化规则解析
GCC的优化逻辑基于程序可见行为一致性,核心规则如下:
- 对于
foo1()的空循环:编译时GCC分析到循环体内没有任何可能修改g_global_cnt的操作(无函数调用、无volatile访问),因此会将全局变量的值缓存到寄存器中,仅在循环开始时从内存加载一次。后续循环直接检查寄存器值,不会再读取内存,导致任务B修改内存中的g_global_cnt后,任务A无法感知,陷入死循环。 - 对于
foo2()的循环:循环体内调用了跨编译单元的dummy_func(),由于编译foo.c时无法看到dummy_func()的实现,GCC会默认假设该函数可能修改任何全局变量(包括g_global_cnt)。因此每次循环都会强制从内存重新加载g_global_cnt的值进行检查,确保能感知到其他任务对该变量的修改,循环可正常退出。 volatile关键字的作用:当变量被声明为volatile时,会告诉编译器该变量可能被当前程序流之外的因素(如其他任务、中断)修改,因此必须每次都从内存读取其值,禁止寄存器缓存,从而避免上述优化导致的问题。
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

