加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

为什么函数指针的入参可以不等于函数原形的入参?——谈谈栈平衡

发布时间:2020-12-15 04:57:46 所属栏目:百科 来源:网络整理
导读:【摘要】 最近在某产品推进再研版本和维护版本分支合一的事项,为有效的隔离新老特性,使用了Bridge模式,其间采用了函数指针,在编译/运行时通过注册不同的具体实现函数,确定特性的具体行为。 一、 问题 先尝试看看,下列代码的编译结果是什么? typedef?v

【摘要】 最近在某产品推进再研版本和维护版本分支合一的事项,为有效的隔离新老特性,使用了Bridge模式,其间采用了函数指针,在编译/运行时通过注册不同的具体实现函数,确定特性的具体行为。

一、问题

先尝试看看,下列代码的编译结果是什么?

typedef?void?(*APP_FUNC)(unsigned?,?unsigned,?unsigned);

void?Called(int?i1,?char?s2)

{

????? printf("%d,%c",i1,s2);

}

void?Callee()

{

??????int?i1 = -1024;

??????char?s2 =?'B';

??????char?s3 =?'C';

????? APP_FUNC fp = (APP_FUNC) Called;

????? fp(i1,s2,s3);

}

欢迎加入学习群【892643663】,获取全套免费C/C++企业实战级课程资源(素材+源码+视频)和编译大礼包

A. 不会出错,因为s3在实际函数Called中未使用。

B. 出错,因为入参数类型不匹配,函数指针入参为unsigned,实际执行函数Called入参为int,char。

C. 出错,因为入参个数不匹配,调用函数指针的入参为3个,实际执行函数Called入参为2个。

二、分析

我们知道C/C++中默认函数参数入栈顺序为从右至左,函数执行前参数入栈,函数完成后参数出栈;也就是说调用函数指针fp(i1,s3)时,参数入栈顺序为s3-> s2-> s1;那么在实际执行函数Called中,因为没有使用到s3,在默认字节对齐的情况下使用s1,s2变量时,unsigned占用的存储空间>=实际入参的存储空间,所以函数功能应该不会受影响。

问题是——Called执行结束时参数出栈,此种情况究竟应该出多少个呢?入栈出栈是否平衡呢?

????2.1?函数的调用约定

打开VC,我们可以看到有__cdecl、 __fastcall、 __stdcall 三种调用约定。通常

__cdecl ? :C/C++默认的函数调用协议;

__stdcall :Windows API默认的函数调用协议;

__fastcall:适用于对性能要求较高的场合,从左开始不大于4字节的参数放入CPU的ECX和EDX寄存器,其余参数从右向左入栈。

使用者可显式的标记函数的调用约定。

????2.2?三种调用方式的汇编展示? ??

对比__cdecl和__stdcall方式下的汇编,Callee调用Called函数前有明显的参数入栈动作(蓝色部分);但是在Called函数结束时,__cdecl直接ret并在调用函数Callee中add esp,0ch恢复栈顶,__stdcall则在函数内部ret 8(红色部分)恢复栈顶,也就是说__stdcall入栈12个字节,出栈8个字节,栈数据残留,后续运行结果未知。

在__fastcall方式下,Callee调用Called函数,从左向右i1,s2未入栈,通过寄存器ecx,edx传递,其余参数(s3)从右向左入栈后,调用Called函数;然后在Called函数中使用ret n恢复栈顶。本例中被调函数Called使用2个寄存器传递进来的参数,ret 0恢复栈顶,也就是说__fastcall入栈4个字节,出栈0个字节,栈数据残留。

栈内数据清除方式简单描述如下:

__cdecl ? :函数调用结束后由函数调用者清除栈内数据。

__stdcall :函数调用结束后由被调用函数清除栈内数据。

__fastcall:函数调用结束后由被调用函数清除栈内数据。

三、标准遵从

在ISO/IEC 9899:1999 (E)中有如下描述:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type,the behavior is undefined.

四、总结

在标准中没有明确定义;虽然在当前既成事实的__cdecl方式下,函数指针和实现函数入参不同,我们定义的接口函数似乎能正确简洁的运行,但将来的事情谁说的准呢?有追求的程序员不会为未来埋坑。

作者|伍小川

欢迎加入学习群【892643663】,获取全套免费C/C++企业实战级课程资源(素材+源码+视频)和编译大礼包

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读