程序集自动跳转到下一个标签

Spi*_*ngo 0 x86 assembly x86-64

我在汇编中编写了一个程序,如下所示:

%macro print 1

    push rbp

    mov rdi, %1
    xor rax, rax

    call _printf

    pop rbp

%endmacro    
section .data
    e db 'Equal', 0
    l db 'Less than', 0
    g db 'Greater than', 0
section .text
        global start
        extern _printf
    start:
        mov rax, 5
        mov rbx, 5
        cmp rax, rbx ; Compare 4 and 5
        je _equal ; je = jump if equal
        cmp rax, rbx
        jl _less ; jl = jump if less
        cmp rax, rbx
        jg _greater ; jg = jump if greater

        ret

_equal:
    print e
_less:
    print l
_greater:
    print g
Run Code Online (Sandbox Code Playgroud)

但是当我运行程序时它跳转到_equal,但它随后跳转到_less, 和_greater。如何禁用此自动跳转?

Cod*_*ray 5

mov rax, 5
Run Code Online (Sandbox Code Playgroud)

这是一种低效的编写方式mov eax, 5。如果您只是将 32 位值移动到 64 位寄存器中,则不需要将 64 位寄存器指定为操作数。只需指定 32 位寄存器,高 32 位将被隐式清零

同样的事情:

xor rax, rax
Run Code Online (Sandbox Code Playgroud)

您可以只写xor eax, eax,并让高 32 位隐式为零。

cmp rax, rbx ; Compare 4 and 5
Run Code Online (Sandbox Code Playgroud)

由于您知道您只是在比较 32 位值,因此您可以编写cmp eax, ebx只比较较低的 32 位值。这更小,更有效。仅cmp rax, rbx当您确实想要比较这些寄存器中的整个 64 位值时才需要。

当然,整个代码有点傻,因为您已经知道 4 与 5 的比较——这不需要在运行时执行。

但是让我们假设这些是运行时值,并且您确实需要进行比较。您仍然只需要进行一次比较。所以这段代码:

cmp rax, rbx ; Compare 4 and 5
je _equal ; je = jump if equal
cmp rax, rbx
jl _less ; jl = jump if less
cmp rax, rbx
jg _greater ; jg = jump if greater
Run Code Online (Sandbox Code Playgroud)

可以简化为:

cmp rax, rbx
je  _equal
jl  _less
jg  _greater
Run Code Online (Sandbox Code Playgroud)

因为条件跳转指令不会改变标志。

但是当我运行程序时它跳转到_equal,但它随后跳转到_less, 和_greater。如何禁用此自动跳转?

正如评论和另一个答案中已经指出的那样,它实际上并没有jumping,因为它没有跳过任何指令。它只是落入下一个标签。

防止这种情况的一种方法(如 user3344003 建议的那样)是在每个 case 之后添加无条件跳转指令。就像是:

    cmp rax, rbx
    je  _equal
    jl  _less
    jg  _greater
finished:
    ret

_equal:
    print e
    jmp   finished
_less:
    print l
    jmp   finished
_greater:
    print g
    jmp   finished
Run Code Online (Sandbox Code Playgroud)

但是,实际上,您所做的只是跳回返回。单ret条指令的大小比一条jmp指令小,而且效率更高,因为不需要采取分支。如果您有一堆需要在返回之前运行的清理代码,则只能使用此模式。在这种简单的情况下,您可以执行以下操作:

    cmp rax, rbx
    je  _equal
    jl  _less
    jg  _greater

_equal:
    print e
    ret
_less:
    print l
    ret
_greater:
    print g
    ret
Run Code Online (Sandbox Code Playgroud)

请注意,我retjg指令之后省略了。您不需要它——相等、更少和更大详尽地涵盖了所有可能性,因此保证采用三个分支中的一个。事实上,这意味着您可以重新排列代码以消除一个分支,利用最初让您感到困惑的失败:

    cmp rax, rbx
    jl  _less
    jg  _greater

    ; fall through to 'equal' case
    print e
    ret

_less:
    print l
    ret

_greater:
    print g
    ret
Run Code Online (Sandbox Code Playgroud)

有趣的事实:如果你用 C 编写它,这与 GCC 将生成的代码基本相同