调用规范与可变参数表

[C/C++] 可变参数表(Variable Arguments)  C/C++支持可变长度的函数参数表,程序员可以通过指针运算并提领来获取函数的任意个参数。由于C/C++默认的__cedcl调用规范规定:函数参数的压栈和弹出操作都经由主调方进行,所以被调函数其实并不不需要知晓参数数量。为了使用这种可变参数函数调用,需要在参数表末尾添加三个句点形成的省略号“...” void g(int a,char* c,...); 省略号之前的逗号是可选的,并不影 阅读详情
 
        语言调用规范是指进行一次函数调用所采用的传递参数的方法,返回值的处理以及调用堆栈的清理。Microsoft C/C++ 语言中采用了五种调用规范,分别是__cdecl, __stdcall, __fastcall,thiscall和nake每一中调用规范都是利用eax作为返回值,如果函数返回值是64位的,则利用edx:eax对来返回值。Nake调用规范非常的灵活,足以独立的一篇文章描述,这里就不再描述nake调用规范。下表列出了前面四种规范调用的特点:
 
关键字堆栈清理者参数传递顺序
__cdecl调用者从右至左
__stdcall被调用者从右至左
__fastcall被调用者从右至左,前两个参数由寄存器ecx,edx传递
thiscall被调用者或者调用者从右至左

  __cdecl 最大好处在于由于是调用者清理栈,它可以处理可变参数,缺点则在于它增加了程序的大小,因为在每个调用返回的时候,需要多执行一条清理栈的指令。
  __stdcall 是在windows程序设计中出现的最多的调用规则,所有的不可变参数的API调用都使用这个规则。
  __fastcall 在windows内核设计中被广泛的使用,由于两个参数由寄存器直接传递,采用这种规则的函数效率要比以上两种规则高。
  thiscall是C++成员函数的默认调用规范,编译期间,这种调用会根据函数是否支持可变参数表来决定采用什么方式清理堆栈。如果成员函数不支持可变参数,那么它就是用参数入栈,ecx保存this指针的方式进行调用,如果成员函数支持可变参数,那么它的调用和__cdecl类似,唯一不同的是将this指针最后压入栈中进行传递。
  调用者和被调用者必须采用同样的规则才能保证程序的正常执行,曾经看到很多程序员犯的错误就是由于调用规范的不一样,致使程序异常,比如:

DWORD ThreadFunc(LPVOID lpParam)
{
//…
}

CreateThread(..,(LPTHREAD_START_ROUTINE)ThreadFunc, …);

  如果在编译期间没有指定编译选项/Gz(指定未指明调用规范的函数采用__stdcall方式),那么编译器自动将ThreadFunc处理成__cdecl调用规范(/Gd),这样可能在线程开始的时候正常执行,然而退出的时候由于堆栈没有正常清理,造成访问违例或者非法指令错误。
  以上说了很多清理栈的问题,那么为什么清理栈很重要呢。堆栈是线程相关的,也就是说每一个线程含有一个堆栈,这个堆栈上保存了局部变量,调用返回地址等很多线程相关的数据,这也是为什么独立运行的线程可以调用同样一个函数而互不干扰的原因。堆栈的特点恐怕大家已经非常熟悉了,那么根据上面的每一种调用,我给出一个简单的图示来说明清理堆栈的重要性,以及为什么上面的例子代码会出错。


图一 这是线程堆栈在运行的时候的样子

  调用前和后esp的差值中间包含了函数参数表,返回地址这样的重要信息,举个简单的调用例子.假设有某个函数定义是这样的:

Int __cdecl func(void* p);

再假设esp调用函数前的数值为0x1234,那么在进入这个函数体内看到的堆栈是这样的:

122C 1230 1234
Next p 

这里的next指调用函数后的下一条指令的位置。调用函数的汇编码:

Push p
Call func
Add esp,4 《--注意这里,由于是cdecl调用,需要调用者清栈。

而一个__stdcall调用的汇编码:

Push p
Call func

  这里没有了add esp,4这个指令,因为在func函数返回的时候自己将esp已经复原了。再来看刚才举的错误的例子,由于强制转换的作用,线程开始函数被设置成了stdcall调用,而实际的线程函数被编译后,并没有执行堆栈的清理工作,线程函数返回的时候,由于堆栈的不正确,当然会发生错误。修改这个bug的方法只要在线程函数的定义前把__cdecl改成_stdcall即可。
  有了上面的例子做基础来理解可变参数表就简单的多了,由于各种调用规范的限定,致使只有__cdecl调用规范可以采用可变参数表。先来看看可变参数表的定义(可以参考sdk目录下src/crt/varargs.h):

typedef char *va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_dcl va_list va_alist;
#define va_start(ap) ap = (va_list)&va_alist
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ap = (va_list)0

  va_list居然被定义成char* ?没错,这实际是用来定义了一个指针,指针的sizeof()就是操作系统可访问的地址空间的大小,也就是CPU相关的字长。_INTSIZEOF宏很简单,就是用来将数据以n的数据大小对齐。va_start宏有点模糊,可是如果你看懂了上面的堆栈数据结构,那么显然它就是获得最后一个固定参数的地址,也就是堆栈上的地址,va_arg先使得ap指向下一个参数,然后取得当前参数的值(注意,这个值正是堆栈上的值),va_end使得取参数过程结束。
  这几个宏完成的动作很简单了,实际就是取得可变参数表在堆栈上的起始位置,然后根据参数类型,依次从堆栈上取出每一个参数。
  本文简单的介绍了微软C/C++支持的调用类型,结合实例描述了规范的实际应用,最后根据CRT提供的源代码分析了可变参数表的实现。

 
Immich vs Nextcloud照片管理实测:自建私有云相册到底选哪个? 本文深度评测了Immich与Nextcloud在私有云相册管理中的表现,涵盖核心功能、性能基准、本土化实践及进阶功能。Immich凭借微服务架构和AI优化在照片管理、移动端体验上表现优异,而Nextcloud则强在生态整合。文章还提供了Docker部署优化和国内网络适配方案,帮助用户根据需求选择最佳方案。 阅读详情

相关推荐

(10)Proteus 8.9上的STM32仿真系列(HAL)---freertos

实验设计:通过cubemxide生成freertos工程,然后创建一个 pc13 led灯间隔200ms闪烁的任务。非常遗憾的是proteus8.9仿真失败,实物上没有问题,但还是把流程过一遍。 一、原理图 实物是淘宝上stm32f103 c8t6的核心板,proteus上没有这个芯片,这里图上大概表示一下,用的是核心板自带的pc13引脚的led灯。 二、cubemxide配置 1.RCC如之前工程一样配置 2、配置SYS timebase Source不要使用systick,自己选择一个定时器,我这

qq_35442550的博客 7342

windows调用规范和可变参数表

用规范与可变参数表作者:阿半  语言调用规范是指进行一次函数调用所采用的传递参数的方法,返回值的处理以及调用堆栈的清理。Microsoft C/C++ 语言中采用了五种调用规范,分别是__cdecl, __stdcall, __fastcall,thiscall和nake每一中调用规范都是利用eax作为返回值,如果函数返回值是64位的,则利用edx:eax对来返回值。Nake调用规范非常的灵活,足

613

Linux-BSP-WIFI驱动移植与调试实战——以RTL8189FS为例

本文以RTL8189FS为例,详细介绍了Linux BSP中WiFi驱动移植与调试的全流程。涵盖硬件原理图分析、内核配置、设备树适配、驱动编译加载及问题排查等关键步骤,提供实用的调试技巧和解决方案,帮助开发者高效完成嵌入式WiFi驱动开发。

moon9的博客 396

gdb 打印字符串长度_从零开始自制操作系统(11):内核打印库与GDB调试(下)

1.C语言调用约定以及参数压栈此小节作为预备知识我们都知道API(Application Programming Interface,应用程序编程接口),我们介绍另外一种接口:ABI(应用程序二进制接口)这个接口定义了程序底层的调用约定。而调用约定指的是不同层级之间或者语言之间的程序相互调用的一种约定。举个例子,对于C语言想要调用有参数的汇编函数的方式,者必须要一个共同遵守的规范才能保证调用的实现...

weixin_34051133的博客 224

从婴儿学步到工业级应用:VLA的‘隐式标定‘原理通俗图解

本文深入解析VLA(Vision-Language-Action)模型的'隐式标定'原理,通过类比婴儿学习机制,揭示其如何替代传统手眼标定技术。文章对比了工业机器人传统标定与VLA数据驱动方法的差异,并可视化神经网络注意力机制,展示其在空间关系编码上的突破性进展。最后探讨了VLA在工业应用中的优势与混合架构解决方案。

pz890123456的博客 323

深入解析FDTD Script中的farfieldpolar3d命令:复电场计算与应用

本文深入解析了FDTD仿真中`farfieldpolar3d`命令的核心功能与应用。作为`farfield3d`的复数版本,该命令能计算并返回球坐标系下的复电场矢量,完整保留振幅与相位信息,为光学天线设计、近远场变换及偏振态分析等高级应用提供了关键数据基础。

grafana6viz的博客 286

java 可变参数 详解(通俗易懂)

java 可变参数,编程中的又一利器!

优快云 / 稀土掘金/ B站 / GitHub: Cyan_RA9 1万+

光电式烟雾传感器实战指南(一) 光迷宫设计与散射原理篇

本文深入解析了光电式烟雾传感器的核心部件——光迷宫的设计原理与光散射机制。文章详细阐述了如何通过精心设计的光学结构实现‘透气不透光’,并重点探讨了前向散射(尤其是135°角)在提升烟雾监测系统灵敏度与可靠性中的关键作用,为DIY爱好者提供了从理论到实践的完整指南。

weixin_29229055的博客 582

java的可变参数

在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数。使用...表示可变长参数,例如在具有可变长参数的方法中可以把参数当成数组使用,例如可以循环输出所有的参数值。{{System.out.println("可变参数:" + string);}}{{System.out.println(test + ",新的可变参数:" + string);}}{{System.out.println(test + ",新的可变参数:" + string);

TimeFriends 1641

window下安装并使用nvm(含卸载node、卸载nvm、全局安装npm)

文章目录一、卸载node二、安装nvm三、配置路径和下载源四、使用nvm安装node五、nvm常用命令六、卸载nvm七、遇到的问题 nvm 全名 node.js version management,顾名思义是一个nodejs的版本管理工具。通过它可以安装和切换不同版本的nodejs。 一、卸载node 如果你已经安装了node,那么你需要先卸载node,如果你没有安装那直接跳过这一步到下一步。 打开控制面板 -> 打开程序和功能 -> 右上角搜索输入node -> 右键卸载 为了确保彻底

HuangsTing的博客 41万+

uniapp [微信小程序] - 详解微信小程序端接入mqtt全流程及示例源码教程,uniApp微信小程序端如何接入MQTT插件并实现连接/断开+发布订阅+消息队列+离线重连等各种示例代码(开箱即用)

uniapp微信小程序接入MQTT,uniapp中使用mqtt,uniapp 微信小程序端接入mqtt示例代码教程,适用于微信小程序的MQTT插件,uniapp开发微信小程序连接mqtt,uniApp微信小程序接入MQTT协议,uniapp vue3微信小程序使用MQTT的小例子接入流程,使用uniapp开发小程序接入mqtt,如何在uniapp项目中使用mqtt,uniapp mqtt的使用示例,mqtt连接报错,如何在项目里集成mqtt,Mqtt协议,主动连接mqtt,订阅消息,实现发布,订阅和接收消息

🏆 前端领域优质创作者 2880

动态的调用可变参数函数

最近,碰到了一个奇怪的问题:如何在函数中动态的调用可变参数函数。例如说,有某个可变参数函数:void Func1(int a, ...) { ... }现在给出一个个数不定的动态数组,把里面的数值按顺序的作为可变参数传递进 Func1 函数中。当然,如果允许改变 Func1 的定义,那么我相信每个人都可以轻松的完成这个任务,而且方法一定也是八仙过海,各显神通。但是如果限定不可以更改 F

yhz的专栏 2002

华为OD机试真题 新系统 - 勇攀数字高峰 (Java/Py/C/C++/Js/Go)

本文整理华为 OD 机试新系统机考真题库,涵盖高频考点精析与完整题目内容,提供 C 语言、Java、C++、Python、Go、JavaScript 多语言源码实现,搭配详细解题思路讲解,并支持在线 OJ 刷题练习,助力高效备考华为 OD 机试新系统。

算法大师! 2089

JAVA可变参数

而可变参数可以保证无法传入 null ,因为传入0个参数时,接收到的实际值是一个空数组而不是 null。例如:A和B都要求排在最后,无论是AB还是BA都不能满足A和B均在队列最后。调用方需要自己先构造 String[],比较麻烦。可变参数用 类型...定义,可变参数相当于数组类型。三个点是固定格式,不可增添空格等。

m0_74754384的博客 780

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Java中可变参数

最近在读《clean code》,想规范一下自己的编码。有一些概念重新回顾一下,遂在此作为记录。 一、什么是可变参数 可变参数:本质就是动态 创建的数组。 // 可变参数,形参列表可以接受0个值,也可以接受n个值。 修饰符 返回值类型 方法名(数据类型…变量名){ 方法体...

Evan's Blog ٩(๑❛ᴗ❛๑)۶ 2580

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Java可变参数遇到的坑

可变参数为JDK1.5时候增加的特性 可变参数:适用于参数个数不确定,类型确定的情况,java把可变参数当做数组处理。 注意: 可变参数必须位于最后一项。 特点: 调用可变参数的方法时,编译器为该可变参数隐含创建一个数组,在方法体中以数组的形式访问可变参数。 例子: public class Lab{ public static void main(String[] args) { System.out.println(add(2, 3)); Syst

sewevi的博客 797

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

C语言可变参数宏定义方法

在GNU C中,宏可以接受可变数目的参数,就象函数一样,例如:  1 2 #define pr_debug(fmt,arg...) \ printk(KERN_DEBUG fmt, ##arg) 用可变参数宏(variadic macros)传递可变参数表  你可能很熟悉在函数中使用可变参

做一个有技术追求的人 2万+

函数调用规范

函数调用规范是指进行一次函数调用时所采用的传递参数的方法,返回值的处理以及调用堆栈的清理.Microsoftc/c++语言中采用了五中调用规范,分别是_cdecl,_stdcall,_fastcall,thiscall和nake.每一种调用都是利用eax返回,如果函数返回值是64位的,则利用edx:eax返回. 各种调用规范的参数传递方法和调用堆栈清理如下:_cdecl  堆栈清理者(

郭海朋的专栏 520
上一篇: 完整ASCII字符表
下一篇: 谈谈C++内存泄漏
undefined_behavior
博客等级 码龄19年 1粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值