函数参数压栈顺序

一、函数原型的格式如下:

[作用域][函数的链接规范]返回值类型[函数的调用规范]函数名(类型1[形参名],类型2[形参名],...)

函数的参数压栈顺序与其中的  函数的调用规范  有关系,函数的调用规范就是描述参数是怎么传递的和由谁平衡堆栈的,当然还有返回值。编译出来的c/c++程式的参数压栈顺序只和编译器相关!

二、函数调用约定的几种类型

__stdcall,__cdecl,__fastcall,__thiscall,__nakedcall,__pascal

三、函数调用约定的主要约束事件

(1)、参数传递顺序

1.从右到左依次入栈:__stdcall,__cdecl,__thiscall
2.从左到右依次入栈:__pascal
(2)、调用堆栈清理
1.调用者清除栈。
2.被调用函数返回后清除栈。

四、几种常用函数调用约定的描述

(1)__cdecl调用约定(The C default calling convention

1、参数是从右向左传递的,也是放在堆栈中。
2、堆栈平衡是由调用函数来执行的。
3、函数的前面会加一个前缀_(_sumExample)

      但是这里我们应该想想为什么不在被调函数内进行堆栈平衡呢?在这里我们应该要考虑类似于像scanf和printf这样的函数,这里我们应该明白这两个函数的参数都是可变的,如果参数不固定的话,在被调用函数内就无法知道参数究竟使用了多少个字节,所以为了实现可变参数,我们必须要在被调函数执行之后我们才知道参数究竟用了多少字节,所以我们在调用者来进行堆栈平衡操作。

(2)__stdcall调用约定,通常用于Win32 Api中

1、参数是从右往左传递的,也是放在堆栈中。
2、函数的堆栈平衡操作是由被调用函数执行的。
3、在函数名的前面用下划线修饰,在函数名的后面由@来修饰并加上栈需要的字节数的空间(对于int __stdcallsumExample (int a, int b),则函数名为_sumExample@8)。

      因为栈的清理(堆栈平衡操作)是由被调用函数执行的。所以使用__stdcall调用约定生成的可执行文件要比__cdecl的要小,因为在每次的函数调用都要产生堆栈清理的代码。当函数有可变个数的参数时会转化为__cdecl调用约定,因为只有调用者才知道参数的数量在每一次的函数调用,因此也只有调用者才能够执行堆栈清理操作。

(3)__fastcall调用约定

     __fastcall见名知其意,其特点就是快。__fastcall函数调用约定表明了参数应该放在寄存器中,而不是在栈中,VC编译器采用调用约定传递参数时,最左边的两个不大于4个字节(DWORD)的参数分别放在ecx和edx寄存器。当寄存器用完的时候,其余参数仍然从右到左的顺序压入堆栈。像浮点值、远指针和__int64类型总是通过堆栈来传递的。

(4)_thiscall调用约定

    它是c++非静态成员函数的默认调用规范,不能使用个数可变的参数。当调用非静态成员函数的时候,this指针直接保存在ECX寄存器中而非压入函数堆栈。其他方面与__stdcall相同。

函数必须指定一个调用规范。特别是在模块之间的逻辑接口中,每一个函数原型的调用规范必须与其实现的调用规范保持一致,否则会出现编译链接错误。特别地,如果你调用了在某个dll中实现的COM对象的方法,而这些方法在创建时没有显示的指定调用规范,那么它们会使用默认环境的调用规范,而此时如果你的程序使用的默认调用规范与它们的调用规范不一致的话,虽然你的程序可以通过编译和链接,但是在运行时就可能导致程序崩溃。

     所以,凡是接口函数都必须显示地指定其调用规范,除非接口函数是类的非静态成员函数。如果不显示的指定调用规范,类的静态成员函数和全局函数采用c/c++默认的函数调用规范或者由工程指定的调用规范,因此最好也为静态成员函数显示的指定调用规范。

原文地址:https://www.cnblogs.com/zzj2/p/3025014.html