这个 gcc 生成的 strlen() mips 循环如何不逐一关闭?
How is this gcc-generated strlen() mips loop not off-by-one?
这是一个非常基本的 strlen()
实现的源代码。
#include <stddef.h>
#include <stdint.h>
extern uintptr_t lx_syscall3(uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t nr);
static void lx_sys_exit(uintptr_t code)
{
lx_syscall3(code, 0, 0, 4001);
while (1);
}
static size_t lx_strlen(char const* s)
{
size_t len = 0;
while (*(s++)) {
len++;
}
return len;
}
int main() {
lx_sys_exit(lx_strlen("HELO"));
while (1);
}
与与此问题无关的 syscall.s
文件一起编译,为 lx_strlen
生成的 GCC 代码内联到 main
(在 -Os
):
004004fc <main>:
4004fc: 3c1c000b lui gp,0xb
400500: 279c8154 addiu gp,gp,-32428
400504: 0399e021 addu gp,gp,t9
400508: 8f828034 lw v0,-32716(gp)
40050c: 27bdffe0 addiu sp,sp,-32
400510: 24424a64 addiu v0,v0,19044
400514: afbc0010 sw gp,16(sp)
400518: afbf001c sw ra,28(sp)
40051c: 00402825 move a1,v0
400520: 00452023 subu a0,v0,a1
# strlen loop block follows
400524: 24420001 addiu v0,v0,1
400528: 8043ffff lb v1,-1(v0)
40052c: 5460fffd bnezl v1,400524 <main+0x28>
400530: 00452023 subu a0,v0,a1
400534: 8f998118 lw t9,-32488(gp)
400538: 24070fa1 li a3,4001
40053c: 00003025 move a2,zero
400540: 04110093 bal 400790 <lx_syscall3>
400544: 00002825 move a1,zero
400548: 1000ffff b 400548 <main+0x4c>
40054c: 00000000 nop
当运行与qemu-mipsel
时,代码正确输出退出状态4
。所以它似乎工作正常,问题是我只是不理解如何它可能工作。请注意 400528
处的偏移量 -1(v0)
。所以循环总是从存储在 v0
中的地址检查前面的字节。因此,到它为零时,减去原始地址应该得到 5
,而不是 4
。知道它是如何工作的吗?
代码使用了 bnezl
指令,该指令对延迟槽指令有特殊处理:只有在分支发生时才会执行。因此,您的代码将始终使用上一次迭代中的 $a0
,因为 400530
处的 subu a0,v0,a1
不会针对退出循环的最后一个执行。请注意,对于零长度字符串,400520
$a0
被归零。
这是一个非常基本的 strlen()
实现的源代码。
#include <stddef.h>
#include <stdint.h>
extern uintptr_t lx_syscall3(uintptr_t a, uintptr_t b, uintptr_t c, uintptr_t nr);
static void lx_sys_exit(uintptr_t code)
{
lx_syscall3(code, 0, 0, 4001);
while (1);
}
static size_t lx_strlen(char const* s)
{
size_t len = 0;
while (*(s++)) {
len++;
}
return len;
}
int main() {
lx_sys_exit(lx_strlen("HELO"));
while (1);
}
与与此问题无关的 syscall.s
文件一起编译,为 lx_strlen
生成的 GCC 代码内联到 main
(在 -Os
):
004004fc <main>:
4004fc: 3c1c000b lui gp,0xb
400500: 279c8154 addiu gp,gp,-32428
400504: 0399e021 addu gp,gp,t9
400508: 8f828034 lw v0,-32716(gp)
40050c: 27bdffe0 addiu sp,sp,-32
400510: 24424a64 addiu v0,v0,19044
400514: afbc0010 sw gp,16(sp)
400518: afbf001c sw ra,28(sp)
40051c: 00402825 move a1,v0
400520: 00452023 subu a0,v0,a1
# strlen loop block follows
400524: 24420001 addiu v0,v0,1
400528: 8043ffff lb v1,-1(v0)
40052c: 5460fffd bnezl v1,400524 <main+0x28>
400530: 00452023 subu a0,v0,a1
400534: 8f998118 lw t9,-32488(gp)
400538: 24070fa1 li a3,4001
40053c: 00003025 move a2,zero
400540: 04110093 bal 400790 <lx_syscall3>
400544: 00002825 move a1,zero
400548: 1000ffff b 400548 <main+0x4c>
40054c: 00000000 nop
当运行与qemu-mipsel
时,代码正确输出退出状态4
。所以它似乎工作正常,问题是我只是不理解如何它可能工作。请注意 400528
处的偏移量 -1(v0)
。所以循环总是从存储在 v0
中的地址检查前面的字节。因此,到它为零时,减去原始地址应该得到 5
,而不是 4
。知道它是如何工作的吗?
代码使用了 bnezl
指令,该指令对延迟槽指令有特殊处理:只有在分支发生时才会执行。因此,您的代码将始终使用上一次迭代中的 $a0
,因为 400530
处的 subu a0,v0,a1
不会针对退出循环的最后一个执行。请注意,对于零长度字符串,400520
$a0
被归零。