64.3 fastcall
这是一种将部分参数通过寄存器传入,其余参数通过栈方式传入的方法。它的执行效率在一些旧时CPU比cdecl/stdcall要好(因为小栈的压力)。然而,在现代的CPU中使用该调用方式不一定能获得更好的性能。
fastcall并没有一个标准化,因此不同的编译器的实现可以不同。这是一个众所周知的警告:如果你有两个DLL,其中第一个DLL调用第二个DLL的函数,它们是又分别不同的编译器使用fastcall调用方式编译出来的,则会有不可预期的后果。
MSVC和GCC两个编译器都是通过ECX和EDX来传递第一个和第二个参数,通过栈进行传递其余参数。栈指针必须被被调用者恢复为初始状态(与stdcall类似)。
Listing 64.4: fastcall
push arg3
mov edx, arg2
mov ecx, arg1
call function
function:
.. do something ..
ret 4
举个例子,我们可以稍微把8.1的示例代码修改一下,增加一个__fastcall
修饰符。
int __fastcall f3 (int a, int b, int c)
{
return a*b+c;
};
下面它编译出来的结果:
Listing 64.5: Optimizing MSVC 2010 /Ob0
_c$ = 8 ; size = 4
@f3@12 PROC
; _a$ = ecx
; _b$ = edx
mov eax, ecx
imul eax, edx
add eax, DWORD PTR _c$[esp-4]
ret 4
@f3@12 ENDP
; ...
mov edx, 2
push 3
lea ecx, DWORD PTR [edx-1]
call @f3@12
push eax
push OFFSET $SG81390
call _printf
add esp, 8
我们可以看到被调用者使用RET N指令来调整栈指针(ESP)。这意味着,我们可以通过这条指令来推断出参数的个数。
64.3.1 GCC regparm
这是一种对fastcall调用方式的某种优化。使用-mregparm编译选项可以设置多少个参数是通过寄存器传递的(最大为3个)。因此,EAX,EDX和ECX寄存器将被使用。
当然,如果指定通过寄存器传参的参数数量小于三个的时候,并没有使用完这三个寄存器。
调用者需要把栈指针恢复为初始状态。
相关例子请参看(19.1.1)。
64.3.2 Watcom/OpenWatcom 编译器
在这里,它被成为“寄存器调用约定”,头四个参数通过EAX,EDX,EBX和ECX传递。其余参数通过栈传递。通过在函数名上添加下划线来区分那些不同的调用约定。