64.3 fastcall

这是一种将部分参数通过寄存器传入,其余参数通过栈方式传入的方法。它的执行效率在一些旧时CPU比cdecl/stdcall要好(因为小栈的压力)。然而,在现代的CPU中使用该调用方式不一定能获得更好的性能。

fastcall并没有一个标准化,因此不同的编译器的实现可以不同。这是一个众所周知的警告:如果你有两个DLL,其中第一个DLL调用第二个DLL的函数,它们是又分别不同的编译器使用fastcall调用方式编译出来的,则会有不可预期的后果。

MSVC和GCC两个编译器都是通过ECX和EDX来传递第一个和第二个参数,通过栈进行传递其余参数。栈指针必须被被调用者恢复为初始状态(与stdcall类似)。

Listing 64.4: fastcall

  1. push arg3
  2. mov edx, arg2
  3. mov ecx, arg1
  4. call function
  5. function:
  6. .. do something ..
  7. ret 4

举个例子,我们可以稍微把8.1的示例代码修改一下,增加一个__fastcall修饰符。

  1. int __fastcall f3 (int a, int b, int c)
  2. {
  3. return a*b+c;
  4. };

下面它编译出来的结果:

Listing 64.5: Optimizing MSVC 2010 /Ob0

  1. _c$ = 8 ; size = 4
  2. @f3@12 PROC
  3. ; _a$ = ecx
  4. ; _b$ = edx
  5. mov eax, ecx
  6. imul eax, edx
  7. add eax, DWORD PTR _c$[esp-4]
  8. ret 4
  9. @f3@12 ENDP
  10. ; ...
  11. mov edx, 2
  12. push 3
  13. lea ecx, DWORD PTR [edx-1]
  14. call @f3@12
  15. push eax
  16. push OFFSET $SG81390
  17. call _printf
  18. 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传递。其余参数通过栈传递。通过在函数名上添加下划线来区分那些不同的调用约定。