如何正确测试条件:je 或 jge

how to test condition properly: je or jge

我有时使用这种模式来迭代一些东西的数组:

    mov [rsp+.r12], r12 ; Choose a register that calls inside the loop won't modify
    mov r12, -1
.i:
    inc r12
    cmp r12, [rbp-.array_size]
    je .end_i
    ; ... program logic ...
    jmp .i
.end_i:
    mov r12, [rsp+.r12]

我知道测试是否相等就足够了,但不应该 "securely" 测试 "greater or equal"(防止不会发生的情况)。

在这种情况下应该使用 je 还是 jge?

我问的是可以减少引入错误的可能性的具体技巧。

我一直有点喜欢测试范围而不是仅仅测试相等性的想法,以防万一意外翻转或其他什么。但在 x86 asm 中,请记住 cmp/jge 不能 on Core2 (in 32-bit mode), but cmp/je can. I thought that was going to be more relevant until I checked Agner Fog's microarch pdf 并且发现它只是 Core2,而不是 Nehalem,无法融合它,因为宏融合根本不起作用在 Core2 上的 64 位模式下。 (后来的微架构没有那个限制,可以宏融合越来越多的组合。)

根据计数器的不同,您通常可以在没有 CMP 的情况下进行倒计时 (dec/jnz)。通常你知道它不需要是 64 位的,所以你可以使用 dec esi / jnz 或其他什么。 dec esi / jge 确实适用于已签名的计数器,但是 dec 没有设置 CF,因此您不能(有用地)使用 JA。

你的循环结构,中间有一个 if() break,最后有一个 jmp,对于 asm 来说不是惯用的。正常是:

mov ecx, 100

.loop:             ; do{
    ;; stuff
    dec ecx
    jge .loop      ; }while(--ecx >= 0)

您可以使用 jg 仅以正 ecx 重新启动循环,即从 100..1 而不是 100..0 开始循环。

在循环中有一个不被采纳的条件分支一个被采纳的无条件分支效率较低。


扩展讨论 关于 saving/restoring r12 的评论:通常你会做这样的事情:

my_func:
    ; push rbp
    ; mov  rbp, rsp      ; optional: make a stack frame

    push   rbx           ; save the caller's value so we can use it
    sub    rsp, 32       ; reserve some space

    imul   edi, esi, 11   ; calculate something that we want to pass as an arg to foo
    mov    ebx, edi       ; and save it in ebx
    call   foo
    add    eax, ebx       ; and use value.  If we don't need the value in rbx anymore, we can use the register for something else later.

    ...  ;; calculate an array size in ecx

    test   ecx, ecx                ; test for the special case of zero iterations *outside* the loop, instead of adding stuff inside.  We can skip some of the loop setup/cleanup as well.
    jz    .skip_the_loop

    ; now use rbx as a loop counter
    mov    ebx, ecx
.loop:
    lea    edi, [rbx + rbx*4 + 10]
    call   bar                     ; bar(5*ebx+10);
    ; do something with the return value?  In real code, you would usually want at least one more call-preserved register, but let's keep the example simple
    dec    ebx
    jnz    .loop
.skip_the_loop:

    add   rsp, 32         ; epilogue
    pop   rbx

    ;pop  rbp             ; pointless to use LEAVE; rsp had to already be pointing to the right place for POP RBX
    ret

注意我们如何在函数内部使用 rbx 做一些事情,但只 save/restore 一次。