我有一个简单的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
的存储位于本地堆栈帧中,所以我选择一个暂存寄存器,用变量的值加载它,递增它,然后更新值通过存储返回结果来更改变量。返回值未使用,所以忽略它。工作完成了。++counter;
- 就在那时,我记得counter
的存储位于本地堆栈帧中,所以我选择一个暂存寄存器,用变量的值加载它,递增它,然后更新值通过存储返回结果来更改变量。返回值未使用,所以忽略它。工作完成了。因为我是一个软件,所以我什至无法理解人类似曾相识的概念,更不用说体验它了。++counter;
- 就那么...等等。每条语句都完美地编译成机器指令,精确地执行正确的操作。 正是您要求我做的事情。如果你想让我在更高的层次上推理代码并弄清楚我是否可以利用这些语句之间的关系,你应该说些什么......
如果计数器变量未声明为 volatile,并且您设置了大小优化(-Os 参数),gcc 将使用以下命令优化该代码 movs rn,#3 str rn,[变量地址]
使用优化级别 0 时,gcc 编译器将识别并遵循寄存器存储类别。 这可以带来巨大的性能提升。 考虑以下函数:
void test(register unsigned *p, register unsigned n)
{
if (!n)
return;
n*=3;
register unsigned *e = p+n;
register unsigned x12345678 = 0x12345678;
do
{
*p += x12345678;
p+=3;
} while(p < e);
}
如果省略了寄存器限定符,则循环将是:
.L4
ldr r3, [r7, #4]
ldr r2, [r3]
ldr r3, [r7, #8]
adds r2, r2, r3
ldr r3, [r7, #4]
str r2, [r3]
ldr r3, [r7, #4]
adds r3, r3, #12
str r3, [r7, #4]
ldr r2, [r7, #4]
ldr r3, [r7, #12]
cmp r2, r3
bcc .L4
但是添加限定符会将其减少为:
.L4:
ldr r2, [r3]
adds r2, r5, r2
str r2, [r3]
adds r3, r3, #12
cmp r3, r4
bcc .L4
相比之下,启用优化后,循环将是(有或没有寄存器限定符):
.L3:
ldr r3, [r0]
ldr r1, .L5
mov ip, r1
add r3, r3, ip
str r3, [r0]
adds r0, r0, #12
cmp r2, r0
bhi .L3
虽然 -O0 版本比优化版本有更多的序言、设置和结尾代码,但使用寄存器存储类可以提高没有优化器的循环生成代码的质量,与优化版本一样好(实际上比! )优化器生成的代码。