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

Assembly 如何正确测试条件:je或jge,assembly,x86,idioms,loop-counter,Assembly,X86,Idioms,Loop Counter,我有时使用此模式来迭代某个数组: 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]

我有时使用此模式来迭代某个数组:

    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]
我理解测试平等就足够了,但不应该“安全地”测试“更大或相等”(防止不会发生的情况)

在这种情况下应该使用je或jge吗


我想问一些具体的技巧,可以减少引入bug的可能性。

我一直喜欢测试范围的想法,而不是仅仅测试相等性,以防一点意外翻转或其他情况。但在x86 asm中,请记住,
cmp/jge
不能在Core2上运行(在32位模式下),但是
cmp/je
可以。我认为这会更加相关,直到我检查发现只有Core2,而不是Nehalem,无法融合,因为在Core2上,宏融合在64位模式下根本不起作用。(后来的微体系结构没有这种限制,宏可以融合越来越多的组合。)

根据计数器的不同,您通常可以在没有CMP的情况下倒计时(dec/jnz)。通常您知道它不需要是64位,所以您可以使用
dec-esi/jnz
或其他任何东西
dec esi/jge
对签名计数器有效,但
dec
未设置CF,因此无法(有效地)使用JA

你的循环结构,在中间有一个<代码> If()断开,最后一个JMP不是ASM的习惯用法。正常情况是:

mov ecx, 100

.loop:             ; do{
    ;; stuff
    dec ecx
    jge .loop      ; }while(--ecx >= 0)
您可以使用jg仅使用正ecx重新启动循环,即从100..1而不是100..0开始循环

在循环中使用未执行的条件分支和执行的无条件分支效率较低


继续讨论有关保存/恢复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进行一些操作,但只保存/恢复一次。

我一直喜欢这样一种想法,即测试一个范围,而不是仅仅测试相等性,以防发生意外或其他情况。但在x86 asm中,请记住,
cmp/jge
不能在Core2上运行(在32位模式下),但是
cmp/je
可以。我认为这会更加相关,直到我检查发现只有Core2,而不是Nehalem,无法融合,因为在Core2上,宏融合在64位模式下根本不起作用。(后来的微体系结构没有这种限制,宏可以融合越来越多的组合。)

根据计数器的不同,您通常可以在没有CMP的情况下倒计时(dec/jnz)。通常您知道它不需要是64位,所以您可以使用
dec-esi/jnz
或其他任何东西
dec esi/jge
对签名计数器有效,但
dec
未设置CF,因此无法(有效地)使用JA

你的循环结构,在中间有一个<代码> If()断开,最后一个JMP不是ASM的习惯用法。正常情况是:

mov ecx, 100

.loop:             ; do{
    ;; stuff
    dec ecx
    jge .loop      ; }while(--ecx >= 0)
您可以使用jg仅使用正ecx重新启动循环,即从100..1而不是100..0开始循环

在循环中使用未执行的条件分支和执行的无条件分支效率较低


继续讨论有关保存/恢复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进行一些操作,但只保存/恢复一次。

我一直喜欢这样一种想法,即测试一个范围,而不是仅仅测试相等性,以防发生意外或其他情况。但在x86 asm中,请记住,
cmp/jge
不能在Core2上进行宏融合(在32位模式下),但
cmp/je
可以。我认为这会更加相关,直到我检查发现只有Core2,而不是Nehalem,无法融合,因为在Core2上,宏融合在64位模式下根本不起作用。(后来的微体系结构没有这个限制,宏可以融合越来越多的组合。)不过,为什么你要展示一些奇怪的r12溢出/重新加载来释放它作为临时计数器使用呢?这完全不相关(而且看起来不像是高效的代码)。当然,有些寄存器已经死了,你可以不用保存就可以使用。@Peter,一个人应该如何正确地写它呢?我认为r12是一个不错的选择,因为像printf这样的循环中的函数调用不会修改调用保存的r12寄存器,并且我们不需要手动保存和恢复调用周围的计数器。请更正,如果它是错误的。哦,这就是你对代码注释的意思吗?当然,r12是一个不错的选择,但是如果您不需要64位计数器(没有REX前缀),那么ebx或ebp可能是更好的选择。最重要的是,在堆栈框架中有一个名为
.r12
的命名点很奇怪。通常,如果你要给某个东西命名,你会使用一个有语义的标签。因此,如果
r12
之前持有
某个值,则您将存储该值。在循环之后,也许用r12做些别的。不,这和我说的正好相反。更新了我的答案,谢谢你让我知道你在简短的评论中迷失了方向,这样我就可以扩展它,而不是浪费我们的时间:)我一直有点喜欢测试一个范围的想法,而不仅仅是为了平等,以防有点意外或什么。但在x86 asm中,请记住,
cmp/jge
不能在Core2上进行宏融合(在32位模式下),但
cmp/je
可以。我认为这会更加相关,直到我检查发现只有Core2,而不是Nehalem,无法融合,因为在Core2上,宏融合在64位模式下根本不起作用。(后来的微体系结构没有这个限制,宏可以融合越来越多的组合。)不过,为什么你要展示一些奇怪的r12溢出/重新加载来释放它作为临时计数器使用呢?这完全不相关(而且看起来不像是高效的代码)。当然有一些寄存器已经死了,你可以不用保存就可以使用@