这个 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 被归零。