我在 c 中编写了一个非常简单的 memset,它在 -O2 之前都可以正常工作,但在 -O3 之前就不行了……
内存集:
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) 这是一个问题。当函数因为 'n' 为零而没有提前退出时,这个推送会在没有匹配的 pop 的情况下无休止地重复,因为它由 (2) 调用。我用UART打印验证了这一点。r2 也从未被触及,那么为什么与零的比较应该成为真的?
请帮助我了解这里发生了什么。编译器是否假设了我可能无法满足的先决条件?
背景:我在我的裸机项目中使用需要 memset 的外部代码,所以我自己开发了。它只在启动时使用一次,而不是性能关键。
/edit:使用以下选项调用编译器:
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