2015-07-20 59 views
0

我有一個函數,它需要3個參數,dest,src0,src1,每個指針指向大小爲12的數據。我提出了兩個版本。一個用C語言編寫,由編譯器優化,另一個用_asm寫。嗯是的。 3個參數?我很自然地做這樣的事情:MSVC彙編函數參數C++ vs _asm

mov ecx, [src0] 
mov edx, [src1] 
mov eax, [dest] 

我有點編譯器迷惑,因爲它認爲應該增加以下內容:

_src0$ = -8      ; size = 4 
_dest$ = -4      ; size = 4 
_src1$ = 8      ; size = 4 
[email protected]@[email protected]@[email protected]@Z PROC ; vm_vec_add_scalar_asm 
; _dest$ = ecx 
; _src0$ = edx 

; 20 : { 

sub esp, 8 
mov DWORD PTR _src0$[esp+8], edx 
mov DWORD PTR _dest$[esp+8], ecx 

; 21 : _asm 
; 22 : { 
; 23 :  mov ecx, [src0] 

mov ecx, DWORD PTR _src0$[esp+8] 

; 24 :   mov edx, [src1] 

mov edx, DWORD PTR _src1$[esp+4] 

; 25 :   mov eax, [dest] 

mov eax, DWORD PTR _dest$[esp+8] 

Function body etc. 

add esp, 8 
ret 0 

什麼的_src0 $ [ESP + 8]等。甚至意味着?爲什麼它在我的代碼之前完成所有這些東西?它爲什麼試圖[顯然]堆疊什麼東西?

相比之下,C++版本只在它的身上,這是非常類似之前以下內容:

_src1$ = 8      ; size = 4 
[email protected]@[email protected]@[email protected]@Z PROC   ; vm_vec_add 
; _dest$ = ecx 
; _src0$ = edx 

mov eax, DWORD PTR _src1$[esp-4] 

爲什麼是這個小足夠了嗎?

回答

3

Mats Petersson的答案解釋了__fastcall。但我想這不完全是你問...

其實_src0$[esp+8]只是意味着[_src0$ + esp + 8],並_src0$如上定義:

_src0$ = -8      ; size = 4 

所以,整個表達式_src0$[esp+8]不過[esp] ..

要明白它爲什麼要做所有這些事情,您應該首先了解Mats Petersson在他的文章__fastcall或更一般地說,什麼是調用約定。詳細信息請參閱他帖子中的鏈接。

假設您已經理解了__fastcall,現在讓我們來看看您的代碼會發生什麼。編譯器正在使用__fastcall。你被調用函數是f(dst, src0, src1),這需要3參數,所以要根據調用約定,當主叫用戶呼叫f,將執行以下操作:

  1. 移動dstecxsrc0edx
  2. src1到堆棧
  3. 按下4個字節返回地址壓入堆棧
  4. 轉到函數的開始地址f

和被叫方f,它的代碼開始時,然後知道在哪裏的參數是:dstsrc0和是在寄存器ecxedx,分別; esp指向4個字節的返回地址,但是它下面的4個字節(即DWORD PTR [esp + 4])恰好是src1

所以,在你的 「C++版」,功能f只是做自己應該做的事:

mov eax, DWORD PTR _src1$[esp-4] 

這裏_src1$ = 8,所以_src1$[esp-4]正是[esp+4]。看,它只是檢索參數src1並將其存儲在eax。然而,這裏有一個棘手的問題。在f的代碼中,如果你想多次使用參數src1,你當然可以這樣做,因爲它總是存儲在棧中,正好在返回地址的下面;但是如果你想多次使用dstsrc0呢?它們在寄存器中,並且可以隨時銷燬。

所以在這種情況下,編譯器應該執行以下操作:在進入函數f之後,它應該記住當前值ecxedx(通過將它們推入堆棧)。這8個字節是所謂的「陰影空間」。這不是在你的「C++版本」中完成的,可能是因爲編譯器知道這兩個參數不會被多次使用,或者它可以以其他方式正確處理它。

現在,您的_asm版本會發生什麼變化?這裏的問題是你正在使用內聯彙編。然後編譯器失去對寄存器的控制,並且它不能假定_asm塊中的寄存器ecxedx是安全的(它們實際上不是,因爲在_asm塊中使用了它們)。因此它被迫在函數的開頭保存它們。

的保存去如下:它首先提出了通過esp 8個字節(sub esp, 8),然後分別移動edxecx[esp][esp+4]

然後它可以安全地進入你的_asm塊。現在想想(如果有的話),圖片是[esp]src0,[esp+4]dst,[esp+8]是4字節的返回地址,而[esp+12]src1。它不再考慮ecxedx

因此,在_asm塊,mov ecx, [src0]你的第一指令,應該被解釋爲mov ecx, [esp],這是相同的

mov ecx, DWORD PTR _src0$[esp+8] 

和相同的其他兩個指令。

在這一點上,你可能會說,啊,它是在做愚蠢的事情,我不希望它浪費時間和空間,有沒有辦法?

那麼有一種方法 - 不要使用內聯彙編......它很方便,但有一個妥協。

您可以在彙編函數f中編寫一個.asm源文件和public它。在C/C++的代碼中,聲明它爲extern 'C' f(...)。然後,當您開始組裝功能f時,您可以直接使用您的ecxedx

0

編譯器決定使用調用約定,該約定使用「在寄存器中傳遞參數」(又稱爲__fastcall)。這允許編譯器在寄存器中傳遞一些參數,而不是推入堆棧,這可以減少調用的開銷,因爲從一個變量移動到一個寄存器比推入堆棧要快,現在它已經在一個寄存器,當我們到達被調用函數時,所以不需要從堆棧中讀取它。

關於調用約定如何在Web上工作的信息有很多。關於x86 calling conventions的維基百科文章是一個很好的起點。