调用约定
- 格式:doc
- 大小:62.50 KB
- 文档页数:10
stdcall调用约定:stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。
在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。
stdcall调用约定声明的语法为(以前文的那个函数为例):int __stdcall function(int a,int b)stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。
以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:push 2 第二个参数入栈push 1 第一个参数入栈call function 调用参数,注意此时自动把cs:eip入栈而对于函数自身,则可以翻译为:push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向aadd eax,[ebp + 0CH] 堆栈中ebp + 12处保存了bmov esp,ebp 恢复esppop ebpret 8而在编译时,这个函数的名字被翻译成_function@8注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。
其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。
从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。
函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。
本文由我司收集整编,推荐下载,如有疑问,请与我司联系为什么调用者必须在cdecl 调用约定中清除堆栈?为什么调用者必须在cdecl 调用约定中清除堆栈?[英]Why does the caller have to clear the stack in the cdecl calling convention? From: en.wikipedia/wiki/X86_calling_conventions来自:http://en.wikipedia/wiki/X86_calling_conventionspush cpush bpush acall function_nameadd esp, 12 ;Stack clearingmov x, eax Why dowe need to explicitly add 12 to ESP to clear the stack since the called function should havepoped the parameters off the stack therefore restoring the stack pointer...?为什么我们需要显式地将12 添加到ESP 以清除堆栈,因为被调用的函数应该将参数从堆栈中取出,因此恢复堆栈指针...?Another question:另一个问题:Theoretically, it would be possible to implement variable parameter functions with the callee taking care of the cleanup right (for instance if you pass the number of arguments onthe stack in a register)?从理论上讲,可以实现变量参数函数,callee 负责清理权限(例如,如果你在寄存器中传递堆栈中的参数数量)?19Because, with the C calling convention, the called function will not pop the parameters. That’s the point of this calling convention.因为,使用C 调用约定,被调用的函数不会弹出参数。
c语言调用汇编语言函数的方式以C语言调用汇编语言函数的方式在编程中,有时需要使用汇编语言来实现一些特定的功能。
但是,由于汇编语言复杂且难以维护,因此我们通常使用高级语言如C语言来编写主要的程序代码。
在这种情况下,我们需要一种方法来调用汇编语言函数,以便在程序中使用它们。
本文将介绍如何使用C 语言调用汇编语言函数。
1. 定义汇编函数我们需要编写一个汇编函数来执行我们需要的操作。
汇编函数可以使用任何汇编语言,但是必须遵循特定的调用约定。
在x86架构中,调用约定指定了函数参数的传递方式、寄存器的使用方式以及栈的使用方式。
以Windows操作系统为例,Windows API使用的是stdcall调用约定,即参数从右往左依次压入栈中,返回值放在EAX寄存器中。
因此,我们需要在编写汇编函数时遵循这个调用约定。
下面是一个使用汇编语言实现计算阶乘的例子:```global factorial ; 导出函数符号section .textfactorial:push ebp ; 保存调用函数的栈帧指针 mov ebp, esp ; 设置当前栈帧指针mov eax, [ebp+8] ; 获取函数参数cmp eax, 1 ; 判断参数是否为1jle .base_case ; 如果是,则返回1dec eax ; 否则,计算(n-1)!push eax ; 保存(n-1)的值call factorial ; 递归调用阶乘函数pop ecx ; 恢复(n-1)的值imul eax, ecx ; 计算n*(n-1)!jmp .done ; 返回结果.base_case:mov eax, 1 ; 如果参数为1,则返回1.done:mov esp, ebp ; 恢复栈指针pop ebp ; 恢复调用函数的栈帧指针 ret ; 返回函数结果```在这个例子中,我们定义了一个名为“factorial”的函数,该函数计算输入参数的阶乘。
__stdcall用例-回复1. 什么是__stdcall?__stdcall是一种在应用程序开发中常见的函数调用约定,用于规定函数调用时参数的传递方式和返回值的返回方式。
它主要用于C和C++编程语言中,是一种Windows特定的函数调用约定。
2. __stdcall与其他函数调用约定有什么区别?__stdcall与其他函数调用约定,如__cdecl和__fastcall等,主要区别在于参数传递的方式和函数栈的清理方式上。
- __stdcall约定规定参数是从右向左依次压入堆栈,调用者负责在调用函数后清理堆栈。
- __cdecl约定规定参数是从右向左依次压入堆栈,但是堆栈的清理工作由被调用函数自己负责。
这使得__cdecl可以在不同编译器和库之间进行交互,但是会增加函数调用的开销。
- __fastcall约定将前两个整型参数存储在寄存器中,能够提高函数调用的性能。
但是这种约定只适用于少量的参数,过多的参数会导致其它参数被压入堆栈。
3. 为什么在Windows编程中常使用__stdcall?在Windows平台上,许多API函数使用了__stdcall约定,开发者需要遵守这个约定来正确调用这些函数。
这主要是为了确保编译器和函数库之间的兼容性,使得函数调用的参数传递和堆栈清理都能够正确执行。
此外,__stdcall约定还有以下优势:- 由于堆栈的清理工作由调用者来完成,可以确保在函数调用结束后堆栈的正确恢复,避免发生堆栈溢出等问题。
- 由于参数是从右向左依次压入堆栈,可以有效地避免参数溢出问题。
- 使用__stdcall约定的函数可以通过函数原型或函数指针的方式传递给其他函数,使得函数调用更加灵活和方便。
4. 如何定义一个使用__stdcall的函数?使用__stdcall约定定义一个函数,需要在函数声明的返回类型前加上__stdcall关键字。
例如:c__stdcall void MyFunction(int a, int b);在函数定义时也需要加上__stdcall关键字,例如:c__stdcall void MyFunction(int a, int b) {函数体}5. __stdcall约定可以有哪些限制?__stdcall约定在参数传递过程中有一些限制:- 参数必须是固定长度的类型,例如整型、指针等。
__stdcall用法stdcall是Windows系统中常用的一个调用约定,它定义了函数调用参数、函数调用栈和函数返回值的传递方式。
由于其高效稳定的特性,stdcall被广泛应用于多种不同的编程语言,包括汇编语言、C/C++和Visual Basic等等。
stdcall的特点可以概括为:1.数的参数书写顺序从右至左,表示参数在栈中的压入顺序。
2.于cdecl调用,由调用者管理函数调用栈的清理,对于stdcall 来说,则由被调用的函数负责清理函数调用栈。
3.于cdecl调用,参数从左至右依次传入,而对于stdcall调用,参数从右至左传递。
4.于stdcall调用,传入参数在调用前就已经存储在栈中,而不是在调用时动态压入栈中。
stdcall在编程汇编过程中是非常有用的,因为它可以为程序员提供一个简单易用的接口,帮助程序员更方便地调用接口函数。
stdcall用于以下几种情况:1.序调用库函数:stdcall具有较高的稳定性,很好地实现了库函数的调用。
2.序调用操作系统函数:由于系统函数运行在内核态,因此要求调用参数和函数返回值传递都有较高的稳定性,stdcall恰恰满足了这种需求。
3.汇编函数和高级语言结合起来:由于stdcall可以简化函数参数的传递,因此在汇编与高级语言结合的情况下,使用stdcall可以大大简化程序的书写,减少出现错误的可能性。
stdcall调用有其自身的优势,因此在现在的软件开发中,它被广泛使用,特别是对于调用Win32库函数或操作系统函数的情况,它可以让程序员更方便地调用函数,让程序更稳定地运行,减少出现Runtime Error的概率。
总之,stdcall用法为程序调用提供了一个非常安全可靠的参数传递机制,它可以大大提高函数的调用效率,有助于降低程序中出现Runtime Error的概率,而且还可以简化汇编程序的书写。
因此,stdcall用法正成为越来越多的开发者所采用的一种编程调用方式。
大模型工具调用(function call)原理及实现在现代计算科学与软件工程领域,大模型工具调用(或称函数调用)是程序执行过程中不可或缺的一部分。
函数调用涉及一系列复杂的机制,包括内存管理、参数传递、栈帧创建与销毁等,它们共同确保程序能够按照既定的逻辑顺序正确执行。
本文将从底层原理出发,逐步深入探讨函数调用的实现细节。
一、函数调用的基本概念函数调用是指程序在执行过程中,根据控制流的需要跳转到指定的函数体执行,待函数执行完毕后,再返回到调用点继续执行后续代码的过程。
函数调用通常涉及以下几个关键步骤:1. 参数传递:将调用函数时提供的实际参数值传递给被调用函数的形式参数。
2. 栈帧创建:在被调用函数开始执行前,系统会为其分配一块栈内存用于存放局部变量、形式参数以及返回地址等信息,这块内存区域称为栈帧(Stack Frame)。
3. 函数体执行:在被调用函数的栈帧中执行函数体内的代码逻辑。
4. 返回值传递:函数执行完毕后,将结果(如果有)返回给调用者。
5. 栈帧销毁:函数执行完毕后,系统回收其栈帧所占用的内存空间。
二、函数调用的实现原理函数调用的实现原理与计算机体系结构和操作系统紧密相关,特别是在内存管理和控制流方面。
以下是函数调用实现的一些关键原理:1. 内存布局与栈的使用:- 程序内存通常分为堆区、栈区、静态/全局存储区和代码区。
函数调用主要涉及栈区的操作。
- 栈(Stack)是一种后进先出(LIFO)的数据结构,系统为每个线程分配一个栈,用于存储函数调用信息和局部变量。
- 每次函数调用时,系统会在栈上创建一个新的栈帧来存储该函数的信息。
栈帧包括函数参数、返回地址、局部变量等。
2. 调用约定(Calling Convention):- 调用约定规定了函数参数如何传递、函数返回值如何处理以及栈如何维护等细节。
- 不同的编译器和平台可能有不同的调用约定,如x86架构上的`cdecl`、`stdcall`等。
在C语言中,假设我们有这样的一个函数:int function(int a,int b)调用时只要用result = function(1,2)这样的方式就可以使用这个函数。
但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。
也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。
为此,计算机提供了一种被称为栈的数据结构来支持参数传递。
栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。
栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。
用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。
用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。
函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。
在参数传递中,有两个很重要的问题必须得到明确说明:当参数个数多于一个时,按照什么顺序把参数压入堆栈函数调用后,由谁来把堆栈恢复原装在高级语言中,通过函数调用约定来说明这两个问题。
常见的调用约定有:stdcallcdeclfastcallthiscallnaked callstdcall调用约定stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall.在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK.stdcall调用约定声明的语法为(以前文的那个函数为例):int __stdcall function(int a,int b)stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:push 2 第二个参数入栈push 1 第一个参数入栈call function 调用参数,注意此时自动把cs:eip入栈而对于函数自身,则可以翻译为:push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向aadd eax,[ebp + 0CH] 堆栈中ebp + 12处保存了bmov esp,ebp 恢复esppop ebp ret 8而在编译时,这个函数的名字被翻译成_function@8注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。
C/C++编译器-cl.exe的命令选项和在IDE中编译相比,命令行模式编译速度更快,并可以避免被IDE产生的一些附加信息所干扰。
本文将介绍微软C/C++编译器命令行模式设定和用法。
1、设置环境变量:PA TH=C:\Program Files\Microsoft Visual Studio 8\VC\binINCLUDE=C:\Program Files\Microsoft Visual Studio 8\VC\include LIB=C:\Program Files\Microsoft Visual Studio 8\VC\lib如果提示找不到mspdb80.dll文件,则从C:\Program Files\Microsoft Visual Studio 8\Common7\IDE下拷贝“msobj80.dll,mspdb80.dll,mspdbcore.dll,mspdbsrv.exe”这四个文件到C:\Program Files\Microsoft Visual Studio 8\VC\bin下即可。
2、命令选项:1) /C:在预处理输出中保留注释语句2) /c:只编译,不连接,相当于在"Build"菜单下选择了"Compile"3) /D:定义常量和宏,与源程序里的#define 有相同效果4) /E:预处理C、C++源文件,将源文件中所有的预编译指令及宏展开,将注释去掉,然后将预处理器的输出拷贝至标准输出设备输出,并且在每个文件的开头和末尾加入#line5) /EH:指定编译器用何种异常处理模型6) /EP:同/E,只是去掉了#line7) /F:设置程序的堆栈大小8) /FA:设置生成何种列表文件(汇编、汇编与机器码、汇编与源码、汇编与机器码以及源码)9) /Fa:指定用/FA设置的列表文件的存放路径及(或)文件名10) /FD:生成文件的相互依赖信息11) /Fd:设置程序数据库文件(PDB)的存放路径及(或)文件名12) /Fe:设置最终可执行文件的存放路径及(或)文件名13) /FI:预处理指定的头文件,与源文件中的#include有相同效果14) /Fm:创建map文件15) /Fo:设置编译后Obj文件的存放路径及(或)文件名16) /Fp:设置预编译文件(pch)的存放路径及(或)文件名17) /FR:生成浏览信息(sbr)文件18) /Fr:同/FR,不同之处在于/Fr不包括局部变量信息19) /G3:为80386处理器优化代码生成20) /G4:为80486处理器优化代码生成21) /G5:为Pentium处理器优化代码生成22) /G6:为Pentium Pro处理器优化代码生成23) /GA:为Windows应用程序作优化24) /GB:为Pentium处理器优化代码生成,使用80386、80486、Pentium、Pentium Pro的混合指令集,是代码生成的默认选项(程序属性选项中Processor对应Blend)25) /GD:为Windows动态库(dll)作优化,此开关在VC6中没有实现26) /Gd:指定使用__cdecl的函数调用规则27) /Ge:激活堆栈检测28) /GF:消除程序中的重复的字符串,并将她放到只读的缓冲区中29) /Gf:消除程序中的重复字符串30) /Gh:在每个函数的开头调用钩子(hook)函数--penter31) /Gi:允许渐进编译32) /Gm:允许最小化rebuild33) /GR:允许运行时类型信息(Run-Time Type Infomation)34) /Gr:指定使用__fastcall的函数调用规则35) /Gs:控制堆栈检测所用内存大小36) /GT:支持用__declspec(thread)分配的数据的fier-safety37) /GX:允许同步异常处理,与/EHsc开关等价38) /Gy:允许编译器将每一个函数封装成COMDA Ts的形式,供连接器调用39) /GZ:允许在Debug build 的时候捕捉Release build的错误40) /Gz:指定使用__stdcall的函数调用规则41) /H:限制外部名字的长度42) /HELP:列出编译器的所有的命令开关43) /I:指定头文件的搜索路径44) /J:将char的缺省类型从signed char改成unsigned char45) /LD:创建一个动态连接库46) /LDd:创建一个Debug版本的动态链接库47) /link:将指定的选项传给连接器48) /MD:选择多线程、DLL版本的C Run-Time库49) /MDd:选择多线程、DLL、Debug版本的C Run-Time库50) /ML:选择单线程版本的C Run—Time库51) /MLd:选择单线程、Debug版本的C Run—Time库52) /MT:选择多线程版本的C Run-Time库53) /MTd:选择多线程、Debug版本的C Run—Time库54) /nologo:不显示程序的版权信息55) /O1:优化使产生的可执行代码最小56) /O2:优化使产生的可执行代码速度最快57) /Oa:指示编译器程序里没有使用别名,可以提高程序的执行速度58) /Ob:控制内联(inline)函数的展开59) /Od:禁止代码优化60) /Og:使用全局优化61) /Oi:用内部函数去代替程序里的函数调用,可以使程序运行的更快,但程序的长度变长62) /Op:提高浮点数比较运算的一致性63) /Os:产生尽可能小的可执行代码64) /Ot:产生尽可能块的可执行代码65) /Ow:指示编译器在函数体内部没有使用别名66) /Ox:组合了几个优化开关,达到尽可能多的优化67) /Oy:阻止调用堆栈里创建帧指针68) /Q1f:对核心级的设备驱动程序生成单独的调试信息69) /QI0f:对Pentium 0x0f错误指令作修正70) /Qifdiv:对Pentium FDIV错误指令作修正71) /P:将预处理输出写到指定文件里,文件的后缀名为I72) /TC:将命令行上的所有文件都当作C源程序编译,不管后缀名是否为.c73) /Tc:将指定的文件当作C源程序编译,不管后缀名是否为.c74) /TP:将命令行上的所有文件都当作C++源程序编译,不管后缀名是否为.cpp75) /Tp:将指定文件当作C++源程序编译,不管后缀名是否为.cpp76) /U:去掉一个指定的前面定义的符号或常量77) /u:去掉所有前面定义的符号或常量78) /V:在编译的obj文件里嵌入版本号79) /vd:禁止/允许构造函数置换80) /vmb:选择指针的表示方法,使用这个开关,在声明指向某个类的成员的指针之前,必须先定义这个类81) /vmg:选择指针的表示方法,使用这个开关,在声明指向某个类的成员的指针之前,不必先定义这个类,但要首先指定这个类是使用何种继承方法82) /vmm:设置指针的表示方法为Single Inheritance and MultipleInheritance83) /vms:设置指针的表示方法为Single Inheritance84) /vmv:设置指针的表示方法为Any class85) /W:设置警告等级86) /w:禁止所有警告87) /X:阻止编译器搜索标准的include 目录88) /Yc:创建预编译头文件(pch)89) /Yd:在所有的obj文件里写上完全的调试信息90) /Y u:在build过程中使用指定的预编译头文件91) /YX:指示编译器若预编译头文件存在,则使用它,若不存在,则创建一个92) /Z7:生成MSC7.0兼容的调试信息93) /Za:禁止语言扩展(Microsoft Extensions to C)94) /Zd:调试信息只包含外部和全局的符号信息以及行号信息95) /Ze:允许语言扩展(Microsoft Extensions to C)96) /Zg:为源文件里面定义的每个函数生成函数原型97) /ZI:生成程序库文件(Pdb)并支持Edit and Continue调试特性98) /Zi:生成程序库文件(pdb),包含类型信息和符号调试信息99) /ZL:从obj文件里去掉缺省的库文件名100) /Zm:设置编译器的内存分配xianzhi101) /Zn:禁止浏览信息文件里面的封装102) /Zp:设置结构成员在内存里面的封装格式103) /Zs:快速检查语法错误--------------------------vc所支持的文件类型1) DSW:全称是Developer Studio Workspace,最高级别的配置文件,记录了整个工作空间的配置信息,她是一个纯文本的文件,在vc创建新项目的时候自动生成2) DSP:全称是Developer Studio Project,也是一个配置文件,不过她记录的是一个项目的所有配置信息,纯文本文件3) OPT:与DSW、DSP配合使用的配置文件,她记录了与机器硬件有关的信息,同一个项目在不同的机器上的opt文件内容是不同的4) CL W:记录了跟ClassWizard相关的信息,如果丢失了clw文件,那么在Class View面板里就没有类信息5) PLG:实际上是一个超文本文件,可以用Internet Explorer打开,记录了Build的过程,是一个日志型文件6) RC:资源描述文件,记录了所有的资源信息,在资源编辑器里作的修改,实际上都是对RC文件的修改7) RC2:附加的资源描述文件,不能直接资源编辑器修改,只能手工添加,可以用来添加额外的资源8) RES:经过资源编辑器编译之后的资源文件,以二进制方式存放9) SBR:编译器生成的浏览信息文件,在代码导航的时候非常有用,她需要在编译时指定/FR或者/Fr开关10) BSC:BSCMAKE.EXE将所有的SBR文件作为输入,经过处理之后输出一个BSC文件,在代码导航的时候实际用到的是BSC 文件11) ILK:当选定渐增型编译连接时,连接器自动生成ILK文件,记录连接信息12) PDB:全称是Program DataBase,即程序数据库文件,用来记录调试信息,是一个相当重要的文件,没有他,程序无法正常调试13) LIB:如果项目输出是Dll的话,一般会输出一个跟项目同名的Lib文件,记录输出的函数信息14) EXP:同Lib,是跟Dll一起生成的输出文件15) PCH:全称是PreCompiled Header,就是预先编译好的头文件,在编译时指定/Y u开关时编译器自动生成VC++编译器中一些常见precompiling 指令介绍我们在利用vc6.0 project wizard生成的代码中,经常看到大量的precompiling指令。
x64汇编第三讲,64位调⽤约定与函数传参.⽬录x64汇编第三讲,64位调⽤约定与函数传参.⼀⼂复习X86传参在x86下我们汇编的传参如下:push eaxcall xxxxxx fun procpush ebp 保存栈底mov ebp,esp 设置ebpsub esp,0C0h 开辟局部变量空间push ebx 保存寄存器环境push esipush edipop edi 恢复寄存器环境pop esipop ebxmov esp,ebp 释放局部变量空间pop ebp 恢复栈底ret 返回,平展, 如果是 C在外平展 add esp,xxx stdcall 则内部平展 ret 4看到上⾯这段代码.我们就应该在脑海中有⼀个⽰意图.我们可以根据上图可以看到.在调⽤函数的时候做了那些事情.1.往栈中存放参数2.将返回地址⼊栈3.保存栈底4.栈内部进⾏⾃⼰的申请空间保存环境以及释放.⼆⼂x64汇编2.1汇编详解在x64下,万变不离其宗.⼤部分跟x86⼀样.如汇编代码为:sub rsp,0x28mov r9,1mov r8,2mov rdx,3mov rcx,4call xxxadd rsp,0x281.传参⽅式⾸先说明⼀下,在X64下,是寄存器传参. 前4个参数分别是 rcx rdx r8 r9进⾏传参.多余的通过栈传参.从右向左⼊栈.2.申请参数预留空间在x64下,在调⽤⼀个函数的时候,会申请⼀个参数预留空间.⽤来保存我们的参数.⽐如以前我们通过push压栈参数的值.相应的栈就会抬⾼.其实x64下,⼀样会申请.只不过这个地⽅在进函数的时候并没有值.进⼊函数之后才会将寄存器的值在拷贝到这个栈中.其实就相当于你还是push了.只不过我是外边申请空间,内部进⾏赋值.如下:sub rsp,0x28 //申请的栈空间为0x28,就相当于我们push rcx rdx r8 r9.只不过只是申请.call xxxxadd rsp,0x28xxx //函数内部mov [rsp - 8],rcxmov [rsp - 0x10],rdxmov [rsp - 0x18],r8mov [rsp - 0x20],r9xxx如下图:我们编写⼀个简单的x64程序.对其反汇编进⾏查看.⾸先开辟我们的参数空间,以及返回地址空间.我们单步⼀下查看可以看⼤开辟了 5*8个字节⼤⼩的空间.然后下⽅的汇编对其寄存器赋值.进⾏传参.说明我们只有4个参数.此时进⼊Call内部.看下栈.3.栈按照16字节对齐现在我们应该明⽩了.在调⽤⼀个函数的时候. 使⽤ *sub rsp,xxx**进⾏抬栈,函数内部则进⾏参数赋值.其实也是相当于push了参数.只不过它不像x86⼀样.在⾥⾯进⾏平栈了.⽽是外⾯进⾏平栈了.那么有个疑问.⽐如说我们就4个参数. 通过上⾯来说.我们应该申请 sub rsp,0x20个字节才对.在CALL的时候x86 x64都是⼀样的会将返回地址⼊栈. 那为什么要rsp,0x28.这样的话会多申请⼀个参数的值哪.原因是这样的.栈要按照16字节对齐进⾏申请.那么还有⼈会说.按照16字节对齐,那么我们的参数已经是16字节对齐了.⽐如为我们4个寄存器申请预留空间. rsp,0x20. (4 * 8 = 32 = 16j进制的 0x20)那为什么还是会申请 rsp,0x28个字节,并且不对齐.其实是这样的.当我们在 Call函数的时候.返回地址会⼊栈.如果按照我们之前申请的rsp,0x20个字节的话.那么当返回地址⼊栈之后,现在总共抬栈⼤⼩是 0x28个字节.并不是16进制对齐. 但是当我们⼀开始就申请0x28个字节.当返回地址⼊栈.那么就是0x28+8 = 0x30个字节. 0x30个字节不是正好跟16字节对齐吗.所以我们的疑问也就没有了.所以申请了0x28个字节,其实多出了的8字节是要跟返回地址⼀样.进⾏栈对齐使⽤.那么申请的这个8字节空间,是没有⽤的.只是为了对齐使⽤.所以x64汇编其实也就搞明⽩了.1.在调⽤函数之前,会申请参数预留空间.(rcx,rdx,r8,r9)2.函数内部,会将寄存器传参的值(rcx,rdx,r8,r9)保存到我们申请的预留空间中.上⾯这两步其实就相当于x86下的 push r9 push r8 push rdx,push rcx3.调⽤约定是__fastcall.传参有rcx rdx,平栈是按照c调⽤约定平栈. 也就是调⽤者平栈.。
调用约定(Calling convention)详解(__stdcall,__cdecl,__fastcall)分类:c/c++/vc 2010-03-12 10:21 233人阅读评论(1) 收藏举报#define CALLBACK __stdcall#define WINAPI __stdcall#define WINAPIV __cdecl#define APIENTRY WINAPI#define APIPRIVATE __stdcall#define PASCAL __stdcall调用约定(Calling convention):决定函数参数传送时入栈和出栈的顺序,由调用者还是被调用者把参薯弹出栈,以及编译器用来识别函数名字的修饰约定。
函数调用约定有多种,这里简单说一下:1、__stdcall调用约定相当于16位动态库中经常使用的PASCAL调用约定。
在32位的VC++5.0中PASCAL调用约定不再被支持(实际上它已被定义为__stdcall。
除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall调用约定。
两者实质上是一致的,即函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。
_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。
VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。
2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。
对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。
另外,在函数名修饰约定方面也有所不同。
_cdecl是C和C++程序的缺省调用方式。
关于C语言中的Pascal调用约定和stdcall调用约定。
以前有点怀疑,WinMain函数既可以用WINAPI调用约定,又可以用PASCAL调用约定,是怎么回事呢?PASCAL不是从左到右压入参数的吗?后来才发现,只有在VC5的时候,微软的编译器才真正支持Pascal调用约定,到后来的VC6,WinMain前面的所谓PASCAL调用约定只是为了和以前的代码和文档保持兼容,它实际上是使用__stdcall的调用约定了。
所以,见到win32程序里的PASCAL宏时不要望文生义,实际上它产生的是stdcall的调用约定!见下面:#define CALLBACK __stdcall#define WINAPI __stdcall#define WINAPIV __cdecl#define APIENTRY WINAPI#define APIPRIVATE __stdcall#define PASCAL __stdcall#define cdecl _cdecl#ifndef CDECL#define CDECL _cdecl#endif几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,首先,需要了解两者之间的区别:WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。
当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除??如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。
这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。
如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。
所以,在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。
那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。
到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。
2.__cdecl,__stdcall是声明的函数调用协议.主要是传参和弹栈方面的不同.一般c++用的是__cdecl,windows里大都用的是__stdcall(API)__cdecl 是C/C++和MFC程序默认使用的调用约定,也可以在函数声明时加上__cdecl关键字来手工指定。
采用__cdecl约定时,函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。
因此,实现可变参数的函数只能使用该调用约定。
由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大。
__cdecl可以写成_cdecl。
__stdcall调用约定用于调用Win32 API函数。
采用__stdcall约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。
由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。
__stdcall可以写成_stdcall。
__fastcall 约定用于对性能要求非常高的场合。
__fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈。
__fastcall可以写成_fastcall3.__stdcall:_stdcall 调用约定相当于16位动态库中经常使用的PASCAL调用约定。
在32位的VC++5.0中PASCAL调用约定不再被支持(实际上它已被定义为__stdcall。
除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall调用约定。
两者实质上是一致的,即函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。
_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。
VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。
_cdecl:_cdecl c调用约定, 按从右至左的顺序压参数入栈,由调用者把参数弹出栈。
对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。
另外,在函数名修饰约定方面也有所不同。
_cdecl是C和C++程序的缺省调用方式。
每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
函数采用从右到左的压栈方式。
VC 将函数编译后会在函数名前面加上下划线前缀。
是MFC缺省调用约定。
在C语言中,假设我们有这样的一个函数:int function(int a,int b)调用时只要用result = function(1,2)这样的方式就可以使用这个函数。
但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。
也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。
为此,计算机提供了一种被称为栈的数据结构来支持参数传递。
栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。
栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。
用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。
用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。
函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。
函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。
在参数传递中,有两个很重要的问题必须得到明确说明:∙当参数个数多于一个时,按照什么顺序把参数压入堆栈∙函数调用后,由谁来把堆栈恢复原装在高级语言中,通过函数调用约定来说明这两个问题。
常见的调用约定有:∙stdcall∙cdecl∙fastcall∙thiscall∙naked callstdcall调用约定stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。
在Microsoft C++系列的C/ C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBA CK。
stdcall调用约定声明的语法为(以前文的那个函数为例):int __stdcall function(int a,int b)stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成:push 2 第二个参数入栈push 1 第一个参数入栈call function 调用参数,注意此时自动把cs:eip入栈而对于函数自身,则可以翻译为:push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,e bp +8指向aadd eax,[ebp + 0CH] 堆栈中ebp + 12处保存了bmov esp,ebp 恢复esppop ebpret 8而在编译时,这个函数的名字被翻译成_function@8注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。
其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。
从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。
函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。
cdecl调用约定cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:int function (int a ,int b) //不加修饰就是C调用约定int __cdecl function(int a,int b)//明确指出C调用约定在写本文时,出乎我的意料,发现cdecl调用约定的参数压栈顺序是和stdcall是一样的,参数首先由有向左压入堆栈。
所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。
由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。
对于前面的function函数,使用cdecl后的汇编码变成:调用处push 1push 2call functionadd esp,8 注意:这里调用者在恢复堆栈被调用函数_function处push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,e bp +8指向aadd eax,[ebp + 0CH] 堆栈中ebp + 12处保存了bmov esp,ebp 恢复esppop ebpret 注意,这里没有修改堆栈MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_f unction,但是我在编译时似乎没有看到这种变化。
由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为:int sprintf(char* buffer,const char* format,...)由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。
fastcallfastcall调用约定和stdcall类似,它意味着:∙函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecx和edx传递,其他参数通过从右向左的顺序压栈∙被调用函数清理堆栈∙函数名修改规则同stdcall其声明语法为:int fastcall function(int a,int b)thiscallthiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。