我用c语言写了一个非常简单的memset,在-O2下可以正常工作,但在-O3下就不行了......。
memset:
void * memset(void * blk, int c, size_t n)
{
unsigned char * dst = blk;
while (n-- > 0)
*dst++ = (unsigned char)c;
return blk;
}
...当使用-O2时,编译成这个汇编。
20000430 <memset>:
20000430: e3520000 cmp r2, #0 @ compare param 'n' with zero
20000434: 012fff1e bxeq lr @ if equal return to caller
20000438: e6ef1071 uxtb r1, r1 @ else zero extend (extract byte from) param 'c'
2000043c: e0802002 add r2, r0, r2 @ add pointer 'blk' to 'n'
20000440: e1a03000 mov r3, r0 @ move pointer 'blk' to r3
20000444: e4c31001 strb r1, [r3], #1 @ store value of 'c' to address of r3, increment r3 for next pass
20000448: e1530002 cmp r3, r2 @ compare current store address to calculated max address
2000044c: 1afffffc bne 20000444 <memset+0x14> @ if not equal store next byte
20000450: e12fff1e bx lr @ else back to caller
这对我来说是有意义的 我注释了这里发生的事情。
当我用-O3编译时,程序崩溃了。我的memset反复调用自己,直到吃光了整个栈。
200005e4 <memset>:
200005e4: e3520000 cmp r2, #0 @ compare param 'n' with zero
200005e8: e92d4010 push {r4, lr} @ ? (1)
200005ec: e1a04000 mov r4, r0 @ move pointer 'blk' to r4 (temp to hold return value)
200005f0: 0a000001 beq 200005fc <memset+0x18> @ if equal (first line compare) jump to epilogue
200005f4: e6ef1071 uxtb r1, r1 @ zero extend (extract byte from) param 'c'
200005f8: ebfffff9 bl 200005e4 <memset> @ call myself ? (2)
200005fc: e1a00004 mov r0, r4 @ epilogue start. move return value to r0
20000600: e8bd8010 pop {r4, pc} @ restore r4 and back to caller
我不明白这个优化版怎么能在没有任何东西的情况下工作?strb
或类似的函数。如果我试着把内存设置为'0'或其他东西,这样函数就不会只在.bss(零初始化)变量上被调用,这并不重要。
(1)这是个问题。这个推送会被无休止地重复,而没有匹配的pop,因为它被(2)调用时,函数不会因为'n'为零而提前退出。我用uart打印验证了这一点。另外r2从来没有被碰过,为什么比较为零会变成真的?
请帮助我理解这里发生了什么。编译器是否假设了一些我可能不满足的先决条件?
背景是这样的。我在裸机项目中使用了需要memset的外部代码 所以我推出了我自己的代码。它只在启动时使用一次,对性能并不关键。
编辑:编译器是用这些选项调用的。
arm-none-eabi-gcc -O3 -Wall -Wextra -fPIC -nostdlib -nostartfiles -marm -fstrict-volatile-bitfields -march=armv7-a -mcpu=cortex-a9 -mfloat-abi=hard -mfpu=neon-vfpv3
你的第一个问题(1)。 这是根据调用惯例,如果你要进行嵌套函数调用,你需要保留链接寄存器,你需要64位对齐。 代码中使用了r4,所以这就是额外保存的寄存器。 没有什么神奇的地方。
你的第二个问题(2)它不是在调用你的memset它是在优化你的代码,因为它认为这是一个低效的memset。 Fuz已经为你的问题提供了答案。
将函数改名为
00000000 <xmemset>:
0: e3520000 cmp r2, #0
4: e92d4010 push {r4, lr}
8: e1a04000 mov r4, r0
c: 0a000001 beq 18 <xmemset+0x18>
10: e6ef1071 uxtb r1, r1
14: ebfffffe bl 0 <memset>
18: e1a00004 mov r0, r4
1c: e8bd8010 pop {r4, pc}
你可以看到这个。
如果你按照Fuz的建议使用-ffreestanding,那么你就会看到这个或类似的东西。
00000000 <xmemset>:
0: e3520000 cmp r2, #0
4: 012fff1e bxeq lr
8: e92d41f0 push {r4, r5, r6, r7, r8, lr}
c: e2426001 sub r6, r2, #1
10: e3560002 cmp r6, #2
14: e6efe071 uxtb lr, r1
18: 9a00002a bls c8 <xmemset+0xc8>
1c: e3a0c000 mov r12, #0
20: e3520023 cmp r2, #35 ; 0x23
24: e7c7c01e bfi r12, lr, #0, #8
28: e1a04122 lsr r4, r2, #2
2c: e7cfc41e bfi r12, lr, #8, #8
30: e7d7c81e bfi r12, lr, #16, #8
34: e7dfcc1e bfi r12, lr, #24, #8
38: 9a000024 bls d0 <xmemset+0xd0>
3c: e2445009 sub r5, r4, #9
40: e1a03000 mov r3, r0
44: e3c55007 bic r5, r5, #7
48: e3a07000 mov r7, #0
4c: e2851008 add r1, r5, #8
50: e1570005 cmp r7, r5
54: f5d3f0a0 pld [r3, #160] ; 0xa0
58: e1a08007 mov r8, r7
5c: e583c000 str r12, [r3]
60: e583c004 str r12, [r3, #4]
64: e2877008 add r7, r7, #8
68: e583c008 str r12, [r3, #8]
6c: e2833020 add r3, r3, #32
70: e503c014 str r12, [r3, #-20] ; 0xffffffec
74: e503c010 str r12, [r3, #-16]
78: e503c00c str r12, [r3, #-12]
7c: e503c008 str r12, [r3, #-8]
80: e503c004 str r12, [r3, #-4]
84: 1afffff1 bne 50 <xmemset+0x50>
88: e2811001 add r1, r1, #1
8c: e483c004 str r12, [r3], #4
90: e1540001 cmp r4, r1
94: 8afffffb bhi 88 <xmemset+0x88>
98: e3c23003 bic r3, r2, #3
9c: e1520003 cmp r2, r3
a0: e0466003 sub r6, r6, r3
a4: e0803003 add r3, r0, r3
a8: 08bd81f0 popeq {r4, r5, r6, r7, r8, pc}
ac: e3560000 cmp r6, #0
b0: e5c3e000 strb lr, [r3]
b4: 08bd81f0 popeq {r4, r5, r6, r7, r8, pc}
b8: e3560001 cmp r6, #1
bc: e5c3e001 strb lr, [r3, #1]
c0: 15c3e002 strbne lr, [r3, #2]
c4: e8bd81f0 pop {r4, r5, r6, r7, r8, pc}
c8: e1a03000 mov r3, r0
cc: eafffff6 b ac <xmemset+0xac>
d0: e1a03000 mov r3, r0
d4: e3a01000 mov r1, #0
d8: eaffffea b 88 <xmemset+0x88>
这看起来就像它简单地内联memset,它知道的那个,而不是你的代码(更快的那个)。
所以如果你想让它使用你的代码,那就坚持使用-O2。 你的代码效率很低,所以不知道为什么你要把它推得更远。
20000444: e4c31001 strb r1, [r3], #1 @ store value of 'c' to address of r3, increment r3 for next pass
20000448: e1530002 cmp r3, r2 @ compare current store address to calculated max address
2000044c: 1afffffc bne 20000444 <memset+0x14> @ if not equal store next byte
如果不把你的代码换成别的东西,它不会比这更好。
Fuz已经回答了这个问题。
用-fno-builtin-memset编译。编译器会识别出这个函数实现了memset,从而用对memset的调用来代替它。一般来说,在编写裸机代码时,你应该用-ffreestanding编译。我相信这也能解决这类问题。
它正在用memset替换你的代码,如果你想让它不这样做,请使用-ffreestanding。
如果你想知道为什么-fno-builtin-memset不工作,那是gcc的人的问题,你可以提交一个票据,让我们知道他们是怎么说的(或者看看编译器的源代码)。