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

举报
华为云社区精选 发表于 2017/10/28 15:10:18 2017/10/28
【摘要】 最近在某产品推进再研版本和维护版本分支合一的事项,为有效的隔离新老特性,使用了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);

}

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

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

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

二、分析

我们知道C/C++中默认函数参数入栈顺序为从右至左,函数执行前参数入栈,函数完成后参数出栈;也就是说调用函数指针fp(i1, s2, 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 三种调用方式的汇编展示    

image.png

image.png

对比__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方式下,函数指针和实现函数入参不同,我们定义的接口函数似乎能正确简洁的运行,但将来的事情谁说的准呢?有追求的程序员不会为未来埋坑。

作者|伍小川

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。