函数调用约定(整理稿)

2023-11-07

函数调用约定 (整理稿)

        Function  calling  convention

 

C语言中,假设我们有这样的一个函数:

int function(int a,int b)

调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。

栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。

函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。

在参数传递中,有两个很重要的问题必须得到明确说明:

·     当参数个数多于一个时,按照什么顺序把参数压入堆栈

·     函数调用后,由谁来把堆栈恢复原装

在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:

·     stdcall

·     cdecl

·     fastcall

·     thiscall

·     naked call

stdcall调用约定

stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPICALLBACK

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指向a

add eax,[ebp + 0CH]  堆栈中ebp + 12处保存了b

mov  esp,ebp         恢复esp

pop ebp

ret 8

而在编译时,这个函数的名字被翻译成_function@8

注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。其中在函数开始处保留espebp中,在函数结束恢复是编译器常用的方法。

从函数调用看,21依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。

 

 

cdecl调用约定

cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:

 

int function (int a ,int b)  //不加修饰就是C调用约定

int __cdecl function(int a,int b)//明确指出C调用约定

 

 

参数从右到左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。对于前面的function函数,使用cdecl后的汇编码变成:

 

调用处

push 2

push 1

call function

add esp,8   注意:这里调用者在恢复堆栈

被调用函数_function处

push ebp     保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复

mov ebp,esp  保存堆栈指针

mov  eax,[ebp + 8H]  堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a

add eax,[ebp + 0CH]  堆栈中ebp + 12处保存了b

mov  esp,ebp         恢复esp

pop ebp

ret         注意,这里没有修改堆栈

 

 

 

MSDN中说,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function,但是我在编译时似乎没有看到这种变化。

由于参数按照从右向左顺序压栈,因此最开始的参数在最接近栈顶的位置,因此当采用不定个数参数时,第一个参数在栈中的位置肯定能知道,只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来,就可以使用不定参数,例如对于CRT中的sprintf函数,定义为:

int sprintf(char* buffer,const char* format,...)

由于所有的不定参数都可以通过format确定,因此使用不定个数的参数是没有问题的。

 

fastcall

fastcall调用约定和stdcall类似,它意味着:

·     函数的第一个和第二个DWORD参数(或者尺寸更小的)通过ecxedx传递,其他参数通过从右向左的顺序压栈

·     被调用函数清理堆栈

·     函数名修改规则同stdcall

其声明语法为:int fastcall function(int a,int b)

 

 

thiscall

thiscall是唯一一个不能明确指明的函数修饰,因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针,因此必须特殊处理,thiscall意味着:

·     参数从右向左入栈

·     如果参数个数确定,this指针通过ecx传递给被调用者;如果参数个数不确定,this指针在所有参数压栈后被压入堆栈。

·     对参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈

为了说明这个调用约定,定义如下类和使用代码:

class A

{

public:

   int function1(int a,int b);

   int function2(int a,...);

};

int A::function1 (int a,int b)

{

   return a+b;

}

#include

int A::function2(int a,...)

{

   va_list ap;

   va_start(ap,a);

   int i;

   int result = 0;

   for(i = 0 ; i < a ; i ++)

   {

      result += va_arg(ap,int);

   }

   return result;

}

void callee()

{

   A a;

   a.function1 (1,2);

   a.function2(3,1,2,3);

}

 

callee函数被翻译成汇编后就变成:

 

//函数function1调用

0401C1D   push        2

00401C1F   push        1

00401C21   lea         ecx,[ebp-8]

00401C24   call  function1           注意,这里this没有被入栈

//函数function2调用

00401C29   push        3

00401C2B   push        2

00401C2D   push        1

00401C2F   push        3

00401C31   lea         eax,[ebp-8]   这里引入this指针

00401C34   push        eax

00401C35   call   function2

00401C3A   add         esp,14h

可见,对于参数个数固定情况下,它类似于stdcall,不定时则类似cdecl

 

 

naked call

这是一个很少见的调用约定,一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码,更特殊的是,你不能用return返回返回值,只能用插入汇编返回结果。这一般用于实模式驱动程序设计,假设定义一个求和的加法程序,可以定义为:

__declspec(naked) int  add(int a,int b)

{

   __asm mov eax,a

   __asm add eax,b

   __asm ret

}

注意,这个函数没有显式的return返回值,返回通过修改eax寄存器实现,而且连退出函数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成:

 

mov eax,[ebp+8]

add eax,[ebp+12]

ret 8

 

注意这个修饰是和__stdcallcdecl结合使用的,前面是它和cdecl结合使用的代码,对于和stdcall结合的代码,则变成:

 

 

__declspec(naked) int __stdcall function(int a,int b)

{

    __asm mov eax,a

    __asm add eax,b

    __asm ret 8        //注意后面的8

}

至于这种函数被调用,则和普通的cdeclstdcall调用函数一致。

函数调用约定导致的常见问题

如果定义的约定和使用的约定不一致,则将导致堆栈被破坏,导致严重问题,下面是两种常见的问题:

1.函数原型声明和函数体定义不一致

2.DLL导入函数时声明了不同的函数约定

以后者为例,假设我们在dll种声明了一种函数为:

 

 

__declspec(dllexport) int func(int a,int b);//注意,这里没有stdcall,使用的是cdecl

使用时代码为:

      typedef int (*WINAPI DLLFUNC)func(int a,int b);

      hLib = LoadLibrary(...);

      DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定

      result = func(1,2);//导致错误

由于调用者没有理解WINAPI的含义错误的增加了这个修饰,上述代码必然导致堆栈被破坏,MFC在编译时插入的checkesp函数将告诉你,堆栈被破坏了。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

函数调用约定(整理稿) 的相关文章

  • JavaScript 测验在提出所有问题之前结束

    我现在正在学习 JavaScript 并且正在创建一个测验 我的测验运行正常 控制台中没有任何错误 但它会跳过问题 有时会在回答所有问题之前结束测验 即使给出正确答案 也会减少时间 我不太确定为什么它会这样做 因为在我看来它的编码是正确的
  • ZLIB 解压缩

    我编写了一个小型应用程序 该应用程序应该解压缩以 gzip deflate 格式编码的数据 为了实现这一点 我使用 ZLIB 库 使用解压缩功能 问题是这个功能不起作用 换句话说 数据不是未压缩的 我在这里发布代码 int decompre
  • 如何将Scheme中的函数应用于另一个函数返回的参数列表?

    假设有两个函数 f 和 v 进一步假设 v 返回长度为 n 的列表 并且 f 需要恰好 n 个参数 我正在Scheme中寻找正确的语法 以将f应用于v返回的列表 如果我使用语法 f v v arguments 然后我收到一个关于 f 需要
  • PowerShell:函数没有正确的返回值

    我编写了一个 powershell 脚本来比较两个文件夹的内容 Dir1 d TEMP Dir1 Dir2 d TEMP Dir2 function Test Diff Dir1 Dir2 fileList1 Get ChildItem D
  • 使用 Visual C++ 在桌面上绘图

    我正在编写一个 opencv 应用程序 使用 Visual Studio VC 控制台应用程序使用激光束进行绘图 我想在桌面上画线 我知道绘图功能在 GDI32 dll 中可用 但对如何将 GDI32 dll 与我的 vc 代码集成感到困惑
  • Javascript:如何禁用提交按钮,直到验证所有字段?

    我有几个工作正常的验证函数 我想用简单的 javascript 编写一个额外的验证 没有 jQuery 等 对于整个表单 根据其他验证函数返回 true 还是 false 来禁用 启用 提交 按钮 我该怎么办 例如 对于我的主要 HTML
  • 在 C 中通过引用传递数组

    我是 C 新手 我有一个疑问 由于 C 函数创建其参数的本地副本 我想知道为什么以下代码按预期工作 void function int array array 0 4 array 1 5 array 2 6 int main int arr
  • Windows Aero - 以编程方式禁用视觉效果

    有谁知道是否有一个 API 可以通过编程方式禁用 启用特定的 Windows 视觉效果 例如 启用透明玻璃 或 启用 Aero Peek 我指的效果是在以下位置配置的效果 系统 高级系统首选项 高级 选项卡 性能设置 自定义 我正在为 Wi
  • P/invoke 函数采用指向结构的指针[重复]

    这个问题在这里已经有答案了 功能如创建进程 http msdn microsoft com en us library ms682425 VS 85 aspx有带有指向结构的指针的签名 在C中我会通过NULL作为可选参数的指针 而不是在堆栈
  • 计算字符串向量中连续数字的函数

    我想创建一个函数 它接受至少 1 个元素的字符串对象并包含数字 2 到 5 并确定是否存在至少 N 长度的连续数字 其中 N 是实际数字值 如果是 则返回字符串 true 否则返回字符串 false 例如 Input 555123 Outp
  • 如何以“正确”的方式处理带有空字节的 Python unicode 字符串?

    Question PyWin32 似乎很乐意将 null 终止的 unicode 字符串作为返回值 我想以 正确 的方式处理这些字符串 假设我得到一个像这样的字符串 u C Users Guest MyFile asy x00 x00sy
  • EnumDisplayDevices 与 WMI Win32_DesktopMonitor,如何检测活动监视器?

    对于我当前的 C 项目 我需要为在大量计算机上连接并处于活动状态的每个监视器检测一个唯一的字符串 研究指出了两种选择 使用 WMI 并查询 Win32 DesktopMonitor 以获取所有活动监视器 使用 PNPDeviceID 来唯一
  • 解密/读取/修改“.automaticDestinations-ms”和/或“.customDestinations-ms”

    有谁知道如何读取 Microsoft 为 JumpList 创建的文件 我想从 JumpList 中清除所有历史记录 最常访问的 最近关闭的 而不清除任务 我尝试使用 win7api 中的 APPID 并清除例如 google chrome
  • 构建适用于 Windows 8 的控制面板小程序

    我开发了一个控制面板小程序 cpl 文件 我们与我们的应用程序一起分发 它在所有以前版本的 Windows 上运行良好 但在 Windows 8 上不行 该小程序根本不显示在控制面板中 以前我们所要做的就是将 cpl 文件放入 system
  • 将对话框项分组到单个“组”(Visual Studio)

    我想创建一个对话框窗口来更改应用程序的设置 下面是 Adob e Reader 的屏幕截图 使用 Spy 后 我猜想 在右侧 所有控件 按钮 组合框 等 都属于 GroupBox 对于左侧TreeView控件中的每个类别 都有一个相应的Gr
  • C++ 将控制台文本颜色设置为 RGB 值

    我想将控制台的文本颜色设置为 RGB 颜色 我创建了一个函数来获取控制台的 ColorTable 并更改其中的颜色 但它不起作用 我不知道如何将文本颜色设置为颜色表中的值 因此我只是更改整个颜色表 但它没有执行任何操作 void setCo
  • 如何删除Windows 10版本1809剪贴板历史记录?

    如果我启用剪贴板历史记录 https www windowscentral com how use new clipboard windows 10 october 2018 update在 Windows 10 版本 1809 上 它将开
  • 验证 EXE 上的 Authenticode 签名 - C++,无需 CAPICOM

    我正在为安装程序 DLL 编写一个函数 以验证系统上已安装的 EXE 文件的 Authenticode 签名 该函数需要 A 验证签名是否有效 B 验证签名者是我们的组织 因为这是在安装程序中 并且因为它需要在较旧的 Win2k 安装上运行
  • 从类 T 获取函数名 (__func__) 和指向成员函数的指针 void(T::*pmf)()

    是否可以写一些f 接受类型的模板函数T和一个指向签名成员函数的指针void T pmf 作为 模板和 或函数 参数并返回const char 指向成员函数的 func 变量 或损坏的函数名称 EDIT 我被要求解释我的用例 我正在尝试编写一
  • 是否有可能劫持标准输出

    我正在尝试使用 C 重定向 Windows XP 上已运行进程的标准输出 我知道如果我自己生成进程 我可以做到这一点 但对于这个应用程序 我更喜欢一个 监听器 我可以附加到另一个进程 这在纯 Net 中可能吗 如果不可能 在 Win32 中

随机推荐

  • Django 出现:Could not parse the remainder: 'date::'Y /m /d''

    在项目中练习中使用动态Url的时候在日期format的时候出现 Could not parse the remainder date Y m d from post date time date Y m d 这里主要是自己跟着练习的时候出现
  • Lodop、C-Lodop页面找不到报404错误解决

    在使用 Lodop C Lodop打印控件时 使用火狐浏览器不报错 换成IE浏览器时报404错误 找不到控件的下载位置 以前的配置如下 1 spring servlet xml中配置 找到打印控件的位置
  • 微信小程序开发1.简易教程

    微信小程序 简易教程 一 基础 第一章 起步 开发小程序的第一步 你需要拥有一个小程序帐号 通过这个帐号你就可以管理你的小程序 跟随这个教程 开始你的小程序之旅吧 申请账号 点击 https mp weixin qq com wxopen
  • C++ 面向对象之引用

    前言 引用是c 区别于c的一个特别好用的特性 它和指针的作用很相似 或者说类似于指针中的常量指针 本文将会从其语法 注意事项 做函数等方面浅谈引用 同时 本文参考了B站视频 链接如下 https www bilibili com video
  • 小白的福音—秒懂UDP协议&TCP协议

    ForeWord 本文介绍了UDP TCP协议的基础知识 主要内容有 UDP TCP协议在TCP IP协议栈中的位置和作用 UDP TCP协议数据段格式 TCP协议如何保证数据传输的可靠性 tips 全文阅读需5min 小伙伴们燥起来 TC
  • 在Linux中配置Samba服务器实现网盘

    在Linux中配置Samba服务器实现网盘 文章目录 在Linux中配置Samba服务器实现网盘 1 安装与基本配置 2 在Windows中使用共享文件夹 3 高级配置 3 1 smb cfg 文件详解 3 2 多用户 多用户组 3 3 典
  • Python网络爬虫实战:爬取携程网酒店评价信息

    这个爬虫是在一个小老弟的委托之下写的 他需要爬取携程网上的酒店的评价数据 来做一些分词和统计方面的分析 然后来找我帮忙 爬这个网站的时候也遇到了一些有意思的小麻烦 正好整理一下拿出来跟大家分享一下 这次爬取过程稍微曲折 各种碰壁 最终成功的
  • Java时间格式化

    Java中的时间格式化是将时间对象转换为指定格式的字符串 或将字符串解析为时间对象 Java提供了丰富的时间格式化API 可以帮助我们方便地处理时间格式化 本篇技术博客将详细介绍Java时间格式化的定义 使用和示例代码 时间格式化 Java
  • 【JavaEE初阶】第八节.多线程(基础篇)阻塞队列(案例二)

    作者简介 大家好 我是未央 博客首页 未央 303 系列专栏 JavaEE初阶 每日一句 人的一生 可以有所作为的时机只有一次 那就是现在 文章目录 一 阻塞队列概论 1 1 阻塞队列的概念与作用 1 2 阻塞队列的应用场景 生产者消费者模
  • Mac 不是私密连接,拒绝访问

    备忘 1 鼠标停在该页面 直接键盘输入 输入时没有任何显示 thisisunsafe 2 刷新页面 参考 Mac chrome 提示您的连接不是私密连接 没有继续访问 20201116更新 同样适用于在打开git图片时 thisisunsa
  • 最简单的大屏适配解决方案---autofit.js

    在工作开发当中 我们避免不了要去做大屏 那么做大屏其实最难的点和最核心的问题就是适配 下面为大家介绍最好用的大屏解决方案 autofit js 一行代码搞定 开袋即食 效果图展示 可根据窗口大小进行自动适配 使用方法 1 npm下载 npm
  • 元代理模型可迁移对抗攻击

    1 引言 该论文是关于黑盒攻击可迁移性的文章 在当前大量的研究中 许多方法直接攻击代理模型并获得的可迁移性的对抗样本来欺骗目标模型 但由于代理模型和目标模型之间的不匹配 使得它们的攻击效果受到局限 在该论文中 作者从一个新颖的角度解决了这个
  • centos 8 配置LVS+ keepalived 高可用

    作者 小刘在C站 个人主页 小刘主页 每天分享云计算网络运维课堂笔记 努力不一定有回报 但一定会有收获加油 一起努力 共赴美好人生 夕阳下 是最美的绽放 树高千尺 落叶归根人生不易 人间真情 前言 现在的努力的程度就是以后生活的好坏 目录
  • 【GPU】显卡算力对比表

    参考链接 英伟达GPU算力表 https developer nvidia com cuda gpus 显卡算力对比表
  • 用paltform框架的驱动形式,编写驱动,应用层程序,在应用层通过ioctl控制LED灯流水,当按键KEY1按下,让风扇转动

    驱动文件 include
  • 期权保证金算法

    看涨期权保证金算法 股指期权当日结算价 沪深300当日收盘价 max 股指期权保证金调整系数 虚值额 沪深300当日收盘价 股指期权保证金调整系数 最低保障系数 合约乘数 看涨期权虚值额 max 股指期权行权价格 沪深300当日收盘价 0
  • pytorch做自己的目标检测模型(训练部分)

    pytorch做自己的目标检测模型 先放上代码的百度云链接 链接 https pan baidu com s 1ms12 2aUvm5M9hjofP8UHA 提取码 8xpf 第一章 制作数据集 要训练自己的pytorch目标检测模型 第一
  • YOLOV7训练自己的数据集(只需四步快速上手)

    论文地址 https arxiv org abs 2207 02696 源码地址 https github com WongKinYiu yolov7 一 数据集 直接把YOLOV5的数据集复制到根目录下 Images文件夹中是图片 Lab
  • coverity代码检测工具介绍_微服务测试之静态代码扫描

    静态代码扫描为整个发展组织增加价值 无论您在开发组织中发挥的作用如何 静态代码扫描解决方案都具有附加价值 拥有软件开发中所需要的尖端功能 最大限度地提高质量并管理软件产品中的风险 背景 微服务架构模式具有服务间独立 可独立开发部署等特点 独
  • 函数调用约定(整理稿)

    函数调用约定 整理稿 Function calling convention 在C语言中 假设我们有这样的一个函数 int function int a int b 调用时只要用result function 1 2 这样的方式就可以使用这