windows的CALLBACK函数修饰符

参考:https://blog.csdn.net/zhang_hui_cs/article/details/8544001

对栈的操作是汇编层面做的,C语言并没有对应的语句能够直接操作栈,

C语言的标准库#include <stdarg.h>中有几个函数可以从栈中取数,但是并不能释放(POP)栈

typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type ); 
void va_end ( va_list ap ); 

看一个例子

void fun(void)
{
    int ret = get_val(8,NULL,0);//语句1
    debug_print("%d\n",ret);//语句2
}
void fun_2(int cnt)
{
    debug_print("%d -> %s\n", cnt, "call fun_2");//语句3
}

fun为调用(主调)函数,get_val、debug_print为被调函数,实参PUSH入栈,POP出栈相关的汇编代码都是由编译器自动生成的,这里还有一个细节问题,对于实参的入栈操作:实参只有调用者例如fun()和fun_2()知晓,显然是实参的PUSH操作被编译到了调用函数中,至于出栈操作,就得分两种情况了

(1)被调函数的形参类型和数目是固定的,从本质上看,入栈的实参内容的字节数是固定的;

(2)被调函数是参数可变的,包括类型可变、数目可变。

对于(1),因为入栈的字节数是固定的,所以出栈的字节数也就固定了,因此调用函数和被调函数都可以完成出栈操作,那么编译器会把出栈指令编译进调用函数、还是被调函数呢?答案是:跟编译器有关,只要方法统一就行。VC编译器默认的操作是把出栈编进主调函数(__cdecl ),有的编译器的默认操作是把出栈编进被调函数(__stdcall)。

对于(2),入栈的字节数只有调用者知道(注意这里的语义,有人可能会说,例如通过printf的第一个参数%xx,被调函数printf也可以知道实参的数目和类型,这种理解方式显然是错误的,混淆了编译期间与运行期间的概念,编译器是不可能在编译时判断函数的实参内容的),对于不同的调用者,例如语句2和语句3,debug_print无论出栈多少字节都是不合适的,因为它无法同时满足所有调用者,所以,出栈的汇编代码只能且必须编译在主调函数中(__cdecl )。

 __stdcall和 __cdecl 函数修饰符的区别就好理解了:__stdcall修饰的函数,其实参的出栈操作位于被调函数;__cdecl 修饰的函数,其出栈操作位于主调函数(这是VC的默认编译方式)。

在WinDef.h中有这个宏#define CALLBACK  __stdcall,这就明确了CALLBACK就是__stdcall

参考https://blog.csdn.net/fxwzzbd/article/details/1796482

后话:对于被调函数清栈这种情况,只是说的把实参占据的栈空间清掉,如果Rn寄存器不足以容纳返回值,被调函数还会把返回值入栈(或者保留一部分实参栈,把这个保留空间上的内容修改为返回值),以供主调函数使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值