为什么这么简单的代码会生成这么多 LDR 和 STR 指令?
Why are so many LDR and STR instructions generated with this simple code?
我有一个简单的 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 -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]
为什么会生成这么多ldr r3, [r7, #4]
和str r3, [r7, #4]
指令?为什么连r7
都需要参与,我们就不能用r3
吗?
没有优化(这显然是),编译器必须做的就是发出指令,这些指令会导致高级语言定义的行为。天真地完全孤立地对待每个陈述是自由的,而这正是它在这里所做的;从编译器的角度来看:
- 一个变量声明:那么,我需要在某个地方存储它,我可以通过创建一个堆栈帧来实现(未显示,但
r7
在这里用作帧指针)。
- 新语句:
counter = 0;
- 好的,我记得 counter
的存储在本地堆栈帧中,所以我只选择一个临时寄存器,生成值 0 并将其存储到在那个位置,工作完成。
- 新语句:
++counter;
- 就在那时,我记得 counter
的存储在本地堆栈帧中,所以我选择了一个临时寄存器,用变量的值加载它,增加它,然后通过存储结果更新变量的值。 return 值未被使用,因此忽略它。工作完成。
- 新语句:
++counter;
- 就在那时,我记得 counter
的存储在本地堆栈帧中,所以我选择了一个临时寄存器,用变量的值加载它,增加它,然后通过存储结果更新变量的值。 return 值未被使用,因此忽略它。任务完成。因为我是一个软件,我连似曾相识的人性概念都无法理解,更别说体验了。
- 新声明:
++counter;
- 那么...
等等。每条语句都完美地编译成机器指令,可以准确地执行正确的操作。 正是你让我做的。如果你想让我在更高层次上推理代码并弄清楚我是否可以利用这些语句之间的关系,你应该说些什么......
如果计数器变量未声明为易变的,并且您设置了大小优化(-Os 参数),gcc 将优化该代码
movs rn,#3
str rn,[变量地址]
我有一个简单的 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 -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]
为什么会生成这么多ldr r3, [r7, #4]
和str r3, [r7, #4]
指令?为什么连r7
都需要参与,我们就不能用r3
吗?
没有优化(这显然是),编译器必须做的就是发出指令,这些指令会导致高级语言定义的行为。天真地完全孤立地对待每个陈述是自由的,而这正是它在这里所做的;从编译器的角度来看:
- 一个变量声明:那么,我需要在某个地方存储它,我可以通过创建一个堆栈帧来实现(未显示,但
r7
在这里用作帧指针)。 - 新语句:
counter = 0;
- 好的,我记得counter
的存储在本地堆栈帧中,所以我只选择一个临时寄存器,生成值 0 并将其存储到在那个位置,工作完成。 - 新语句:
++counter;
- 就在那时,我记得counter
的存储在本地堆栈帧中,所以我选择了一个临时寄存器,用变量的值加载它,增加它,然后通过存储结果更新变量的值。 return 值未被使用,因此忽略它。工作完成。 - 新语句:
++counter;
- 就在那时,我记得counter
的存储在本地堆栈帧中,所以我选择了一个临时寄存器,用变量的值加载它,增加它,然后通过存储结果更新变量的值。 return 值未被使用,因此忽略它。任务完成。因为我是一个软件,我连似曾相识的人性概念都无法理解,更别说体验了。 - 新声明:
++counter;
- 那么...
等等。每条语句都完美地编译成机器指令,可以准确地执行正确的操作。 正是你让我做的。如果你想让我在更高层次上推理代码并弄清楚我是否可以利用这些语句之间的关系,你应该说些什么......
如果计数器变量未声明为易变的,并且您设置了大小优化(-Os 参数),gcc 将优化该代码 movs rn,#3 str rn,[变量地址]