• 【系统篇】小议三种函数调用约定


    小议三种函数调用约定

    __cdecl__stdcall__fastcallC/C++里中经常见到的三种函数调用方式。其中__cdeclC/C++默认的调用方式,__stdcallwindows API函数的调用方式,只不过我们在头文件里查看这些API的声明的时候是用了WINAPI的宏进行代替了,而这个宏其实就是__stdcall了。

    三种调用方式的区别相信大家应该有些了解,这篇文章主要从实例和汇编的角度阐述这些区别的表现形态,使其对它们的区别认识从理论向实际过渡。

    __cdecl:    C/C++默认方式,参数从右向左入栈,主调函数负责栈平衡。

    __stdcall:            windows API默认方式,参数从右向左入栈,被调函数负责栈平衡。

    __fastcall:   快速调用方式。所谓快速,这种方式选择将参数优先从寄存器传入(ECXEDX),剩下的参数再从右向左从栈传入。因为栈是位于内存的区域,而寄存器位于CPU内,故存取方式快于内存,故其名曰“__fastcall”。

    下面从实例来认识一下这三种调用约定。先来看一个简单的不能再简单的程序了:

     

    三个函数的内容都是一样的,不同的是使用了三种调用的方式。我们先来看看在main函数调用三个函数的时候的汇编代码:

     

             按照上面说的那样,__cdecl按照参数从右向左的方式进入栈区,注意Fun1()Fun3()的区别,Fun1()call Fun1()之后执行了add esp,8。这一操作正是我们前面所说的进行栈的平衡。调用函数之前连续进行了两次push操作将函数所需的实参52先后压入了栈区,调用完成后,我们需要恢复调用前的状态,则需调整栈顶指针esp的位置,这一工作由谁来完成就决定了两种函数调用方式__cdecl(主调函数完成)和__stdcall(被调函数完成)的产生。上图我们看到了__cdecl中由主调函数完成了,那么__stdcall呢,在被调函数Fun3()中,转向被调函数结尾处的代码,我们看到了这一句:

     

    那么Fun1()结尾处又是如何呢?

     

     

    看到了吧,这个ret指令后面跟没跟值就决定了函数返回是栈指针ESP需要增加的量。这样,不需要主调函数再调用add指令为ESP操作平衡栈区,节约了程序的开销,一条指令开销小,如果十万百万个这样的调用,这个开销就明显了。

             说完了__cdecl__stdcall,再来看看__fastcall,如前面图看到的调用时并未使用push指令向栈里传参数,而是使用了

      Mov  edx, 5

      Mov  ecx, 2

    两条指令。这样直接将参数传入寄存器,被调函数在执行的时候直接从寄存器取值即可,省去了从栈里取出来给寄存器,再从寄存器取出来放入内存。

             不过,说个题外话,ecx寄存器经常作为计数和C++this指针的传递媒介。在这种情况下,情况又是怎样的呢,下次分析C++操作符 new 的时候再予以讨论。ecx做计数器时,需要将ecx中存储的实参先压入栈区,计数操作完成后再pop出来。如此一来,这个fastcall倒显得不那么fast了。呵呵。

             当然,上面所说的这些操作都是由编译器在背后为我们完成的,开发人员无需关心这些操作,对我们是透明的。不过,知其然更知其所以然方能立于不败之地!

  • 相关阅读:
    CodeForces 1332D Walk on Matrix
    CodeForces 1324F Maximum White Subtree
    CodeForces-1324E-Sleeping-Schedule
    CodeForces-1324D-Pair-of-Topics
    理解字节序 大端字节序和小端字节序
    公钥 私钥 数字证书概念理解
    简要介绍 X Window System (又称为X11 or X)
    动态规划的简洁说明
    二分法的研究
    两个变量的内容交换
  • 原文地址:https://www.cnblogs.com/xuanyuan/p/3917615.html
Copyright © 2020-2023  润新知