这一文章中我们主要分析VS平台对于函数调用的编译处理,首先我们看一个简单的例子,代码如下:
void Hello(){
}
int main(){
Hello();
}
然后在VS Command Prompt下面用cl -FA main.cpp 编译一下,你会得到一个汇编的文件main.asm,其内容如下:
;
Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE E:\bossjue\main.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC ?Hello@@YAXXZ ; Hello
; Function compile flags: /Odtp
_TEXT SEGMENT
?Hello@@YAXXZ PROC ; Hello
; File e:\bossjue\main.cpp
; Line 1
push ebp
mov ebp, esp
; Line 2
pop ebp
ret 0
?Hello@@YAXXZ ENDP ; Hello
_TEXT ENDS
PUBLIC _main
; Function compile flags: /Odtp
_TEXT SEGMENT
_main PROC
; Line 4
push ebp
mov ebp, esp
; Line 5
call ?Hello@@YAXXZ ; Hello
; Line 6
xor eax, eax
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
别怕,有我给你们解释这些代码,这些代码可以说其实都不用看,只需要看有标记;Line 1、;Line 2……这些行就行了,首先在Lin 4,这是main函数的开始,里面首先保存了栈帧,然后Lin 5那里跳到Hello函数里面去执行Hello里面的代码。也就是说,如果我们写以下的代码也应该是对的。
void Hello(){
}
int main(){
__asm{
call Hello
}
}
可以编译,看到程序正常运行,说明我们的call调用是成功的,在此说明了一点,函数调用最后关键的一步是call调用。
上面只是简单的一个Hello函数,下面我们用难一点的去测试,我们写如下代码:
int Hello(){
return 100;
}
int main(){
Hello();
}
这个代码和上面代码的区别在于这个函数Hello 有返回值,同样地用cl -FA main.cpp编译得到main.asm,打开main.asm看到其内容如下:
;
Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE E:\bossjue\main.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC ?Hello@@YAHXZ ; Hello
; Function compile flags: /Odtp
_TEXT SEGMENT
?Hello@@YAHXZ PROC ; Hello
; File e:\bossjue\main.cpp
; Line 1
push ebp
mov ebp, esp
; Line 2
mov eax, 100 ; 00000064H
; Line 3
pop ebp
ret 0
?Hello@@YAHXZ ENDP ; Hello
_TEXT ENDS
PUBLIC _main
; Function compile flags: /Odtp
_TEXT SEGMENT
_main PROC
; Line 5
push ebp
mov ebp, esp
; Line 6
call ?Hello@@YAHXZ ; Hello
; Line 7
xor eax, eax
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
在Line2里面可以看到有一句mov eax, 100,联想到Hello里面返回100,可以猜测VS里面是用eax传递返回值的,其实确实是这样的,可以用下面的代码测试:
#include <iostream>
using namespace std;
int Hello(){
__asm{
mov eax, 100
}
}
int main(){
cout<<Hello()<<endl;
}
可以看到输出100,说明我了们的结果是对的,函数的返回值是存放在eax进而的。
下面我们再给Hello传递参数试试,来进一步说明参数传递的原理代码如下
void Hello(int iVA, int iVB){
}
int main(){
Hello(3, 5);
}
同样的得到汇编的代码如下:
;
Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE E:\bossjue\main.cpp
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC ?Hello@@YAXHH@Z ; Hello
; Function compile flags: /Odtp
_TEXT SEGMENT
_iVA$ = 8 ; size = 4
_iVB$ = 12 ; size = 4
?Hello@@YAXHH@Z PROC ; Hello
; File e:\bossjue\main.cpp
; Line 1
push ebp
mov ebp, esp
; Line 3
pop ebp
ret 0
?Hello@@YAXHH@Z ENDP ; Hello
_TEXT ENDS
PUBLIC _main
; Function compile flags: /Odtp
_TEXT SEGMENT
_main PROC
; Line 5
push ebp
mov ebp, esp
; Line 6
push 5
push 3
call ?Hello@@YAXHH@Z ; Hello
add esp, 8
; Line 7
xor eax, eax
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
可以看到,其实不过是把参数用push传递进了stack里面,然后就是一个call而已,当然是主调函数清除stack(一句add esp而已)
为了测试,我们写下如下的代码
#include <stdio.h>
int main(){
char *lpstrFormat = "%s %d";
char *lpStr = "Hello, this year is: ";
__asm{
push 2012
push lpStr
push lpstrFormat
call printf
add esp, 12
}
}
然后在命令行里面运行得到了你想要的结果,呵呵。
当然,参数怎么传递的和谁清除stack这个由调用方式决定的,调用方式一般有三种__stdcall, __cdecl, __fastcall,读都可以在网上baidu一下,或者过几天我自己写文章说明。
到此,一般函数调用的过程已经完全说完了,如果想知道class的成员函数的调用过程,我在下一文章中说明。