簡體   English   中英

裝配-交換功能-為什么不起作用?

[英]Assembly - Swap function - Why it will not work?

我需要創建一個將&x的值與&y的值交換的函數(意味着交換*(&y)和*(&x)。

Swap:

    push EBP
    mov EBP,ESP
    mov EBX, [EBP+12] ; ebx = *x
    mov EAX, DWORD [EBX] ;eax = ebx = *x
    mov DWORD [EBP-4], EAX ; [ebp-4] = eax =*x
    mov EDX, [EBP+8] ; edx = *y
    mov EAX, DWORD [EDX] ; eax = *edx = *y
    mov DWORD [EBX], EAX ; ebx = eax = *y
    mov EAX, DWORD [EBP-4] ; eax = *x
    mov DWORD [EDX], EAX ; edx = *x
    pop EBP ; ebx = *y and edx = *x
    ret

我這樣稱呼它:

    // call Swap
    push x
    push y
    call swap

我不明白為什么它不起作用。 我添加了一些評論,以解釋我對此的理解。 我的實現有什么問題? 我該如何解決?

在[EBP-4]中訪問雙字時,實際上並沒有在使用的堆棧上保留內存。 它可能會被諸如中斷例程,信號處理程序,異步調用的過程之類的東西覆蓋,無論您的OS是否適用。

代碼應改為:

swap:
    push  EBP
    mov   EBP,ESP           ; make a traditional stack frame

    sub   ESP, 4         ; reserve memory for a local variable at [EBP-4]

    mov   EBX, [EBP+12]        ; ebx = &x
    mov   EAX, DWORD [EBX]     ; eax = x
    mov   DWORD [EBP-4], EAX   ; [ebp-4] = eax = x
    mov   EDX, [EBP+8]         ; edx = &y
    mov   EAX, DWORD [EDX]     ; eax = y
    mov   DWORD [EBX], EAX     ; *&x = y
    mov   EAX, DWORD [EBP-4]   ; eax = x reloaded from the local
    mov   DWORD [EDX], EAX     ; *&y = x

    leave          ; remove locals (by restoring ESP), restore EBP

    ret

另外,請確保將變量xy的地址(而不是變量的值)作為參數傳遞。 push x + push y將在NASM中傳遞xy的地址,但在TASM和MASM中它們將傳遞xy值。

除了Alexey的錯誤修正外,您還可以使其效率大大提高。 (當然,在呼叫站點內聯交換和優化效果更好。)

無需在堆棧上放置本地臨時文件:您可以重新加載其中一個地址兩次,或保存/還原ESI並將其用作臨時文件。

您實際上是在破壞EBX,而EBX在所有常規C調用約定中都保留了調用。 在大多數32位x86調用約定中,EAX,ECX和EDX是無需保存/恢復即可使用的三個調用占據寄存器,而其他寄存器則保留調用。 (因此,調用者希望您不要破壞它們的值,因此只有在放回原始值后才能使用它們。這就是為什么在將EBP用作幀指針之后必須還原EBP的原因。)


編譯交換功能的獨立(非嵌入式)定義時, gcc -O3 -m32作用是保存/恢復EBX,因此它具有4個寄存器供您使用。 clang選擇ESI。

void swap(int *px, int *py) {
    int tmp = *px;
    *px = *py;
    *py = tmp;
}

在Godbolt編譯器瀏覽器上

# gcc8.2 -O3 -m32 -fverbose-asm
# gcc itself emitted the comments on the following instructions
swap:
        push    ebx     #
        mov     edx, DWORD PTR [esp+8]    # px, px
        mov     eax, DWORD PTR [esp+12]   # py, py
        mov     ecx, DWORD PTR [edx]      # tmp, *px_3(D)
        mov     ebx, DWORD PTR [eax]      # tmp91, *py_5(D)
        mov     DWORD PTR [edx], ebx      # *px_3(D), tmp91
        mov     DWORD PTR [eax], ecx      # *py_5(D), tmp
        pop     ebx       #
        ret  

# DWORD PTR is the gas .intel_syntax equivalent of NASM's DWORD
# you can just remove them all because the register implies an operand size

這也避免了制作遺留的堆棧框架。 如果需要,可以將-fno-omit-frame-pointer到編譯器選項中,以查看帶有幀指針的代碼源。 (Godbolt將重新編譯並向您顯示asm。非常方便的網站,供您探索編譯器選項和代碼更改。)

64位調用約定已在寄存器中包含args,並且具有足夠的暫存寄存器,因此我們僅獲得4條指令,效率更高。


正如我提到的,另一種選擇是兩次重新加載一個指針args:

swap:
       # without a push, offsets relative to ESP are smaller by 4
        mov     edx, [esp+4]    # edx = px   reused later
        mov     eax, [esp+8]    # eax = py   also reused later
        mov     ecx, [edx]      # ecx = tmp = *px   lives for the whole function

        mov     eax, [eax]      # eax = *py   destroying our register copy of py
        mov    [edx], eax       # *px = *py;  done with px, can now destroy it

        mov     edx, [esp+8]   # edx = py
        mov    [edx], ecx       # *py = tmp;
        ret  

只有7條指令,而不是8條指令。兩次加載相同的值非常便宜,並且亂序執行意味着快速准備好存儲地址並不是問題,即使按照程序順序,這只是加載之前的一條指令地址。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM