Gcc 为什么用这个简单的代码生成这么多LDR和STR指令?
我有一个简单的C程序:Gcc 为什么用这个简单的代码生成这么多LDR和STR指令?,gcc,assembly,arm,compiler-optimization,cortex-m,Gcc,Assembly,Arm,Compiler Optimization,Cortex M,我有一个简单的C程序: int main(){ unsigned int counter = 0; ++counter; ++counter; ++counter; return 0; } 我正在使用以下编译标志: arm-none-eabi-gcc -c -mcpu=cortex-m4 -march=armv7e-m -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -DPART_TM4C123GH6PM
int main(){
unsigned int counter = 0;
++counter;
++counter;
++counter;
return 0;
}
我正在使用以下编译标志:
arm-none-eabi-gcc -c -mcpu=cortex-m4 -march=armv7e-m -mthumb
-mfloat-abi=hard -mfpu=fpv4-sp-d16 -DPART_TM4C123GH6PM -O0
-ffunction-sections -fdata-sections -g -gdwarf-3 -gstrict-dwarf
-Wall -MD -std=c99 -c -MMD -MP -MF"main.d" -MT"main.o" -o"main.o" "../main.c"
(为了简洁起见,删除了一些-I指令)
请注意,我故意使用-O0
禁用优化,因为我想了解编译器如何优化
这将编译为ARM Cortex-M4的以下部件:
6 unsigned int counter = 0;
00000396: 2300 movs r3, #0
00000398: 607B str r3, [r7, #4]
7 ++counter;
0000039a: 687B ldr r3, [r7, #4]
0000039c: 3301 adds r3, #1
0000039e: 607B str r3, [r7, #4]
8 ++counter;
000003a0: 687B ldr r3, [r7, #4]
000003a2: 3301 adds r3, #1
000003a4: 607B str r3, [r7, #4]
9 ++counter;
000003a6: 687B ldr r3, [r7, #4]
000003a8: 3301 adds r3, #1
000003aa: 607B str r3, [r7, #4]
为什么生成了这么多
ldr3、[r7,#4]
和strr3、[r7,#4]
指令?为什么r7
甚至需要参与,难道我们不能只使用r3
?而不进行优化(这显然是),编译器必须做的就是发出指令,导致更高级语言定义的行为。它可以完全孤立地天真地对待每一项声明,而这正是它在这里所做的;从编译器的角度来看:
- 一个变量声明:那么,我需要一个存储它的地方,我可以通过创建一个堆栈帧来实现这一点(未显示,但是
在这里被用作帧指针)r7
- 新语句:
计数器=0代码>-好的,我记得
的存储在本地堆栈帧中,所以我只需选择一个暂存寄存器,生成值0并将其存储到该位置,作业完成计数器
- 新语句:
-就在那时,我记得++计数器
计数器的存储在本地堆栈帧中,所以我选择一个暂存寄存器,用变量的值加载它,增加它,然后通过存储结果来更新变量的值。返回值未使用,因此请忽略它。工作完成了
- 新语句:
-就在那时,我记得++计数器
计数器的存储在本地堆栈帧中,所以我选择一个暂存寄存器,用变量的值加载它,增加它,然后通过存储结果来更新变量的值。返回值未使用,因此请忽略它。工作完成了。因为我是一个软件的一部分,我甚至不能理解人类对似曾相识的概念,更不用说体验它了
- 新语句:
++计数器代码>-那就
等等。每一条语句,完美地编译成机器指令,做正确的事情。正是你让我做的。如果您想让我在更高的层次上对代码进行推理,并确定我是否可以利用这些语句之间的关系,那么您应该说点什么…如果计数器变量未声明为volatile,并且如果您为size(-Os参数)设置了优化,gcc将使用 movs rn,#3
str rn,[变量地址]您忘记启用优化了吗?您也没有说您正在使用的编译器。r7显然被用作局部变量的基址,在本例中,r7+4是计数器的地址。如前所述,似乎未启用优化。优化编译器可能会检测到计数器从未在main之外使用,并完全忽略它,因此main()只会变成一个返回值0。你完全正确,如果我使用
-O1
,那么所有指令都会被删除,函数返回0。-O0
会在每个语句后将所有内容溢出到内存中,因此,您可以在调试器中更改任何内容,它将产生“预期”效果。这就是为什么-O0
代码是如此嘈杂和可怕,作为人类阅读。它不仅仅是“未优化”,它还反映了更多关于gcc内部的信息,而不是简单的由人直接翻译成asm的内容。实际上,您应该编写函数,获取参数并返回结果,这样它们就不会在-O3
上进行优化。执行此操作可获得格式良好的asm,并在编辑后自动重新编译。(不幸的是,Matt安装的最新ARM编译器仅为g++4.8,但对于C++11 std::atomic来说,如果您想在启用优化的情况下强制内存访问,这仍然足够新。)或者换句话说,实际上,禁用了优化(默认的-O0
)=gcc/clang的调试模式,因此,他们有意将每个C语句编译成单独的asm块。这允许GDB使用jump
命令跳转到其他源代码行,让一切都像在C抽象机器中跳转一样工作-O0
不仅仅是试图优化,它还需要进行反优化,就像一切都是不稳定的一样。