关于函数调用约定的理解

    继续探索昨天那个问题的深层原因,为什么要加前缀下划线呢?今天各方查找,大约是这个意思(也不知道理解的完全不):

    函数调用的时候,父子函数间的数据传递(参数、返回值),只能依靠两种硬件:寄存器和内存(不要说磁盘,那速度。。。)!硬件只有两种,方式可以有多种:寄存器直接传送,栈(内存)传送,混合传送(寄存器、栈一起来)。每种传送方式还得明确规定怎么个传法:比如多个参数利用栈传送得规定进出栈的顺序,谁(父函数还是子函数)负责清栈等等。这些函数调用时的数据传递规则就叫函数调用约定。

    C 语言函数采用的函数调用约定叫cdecl,cdecl 调用约定规定:函数调用的时候,用栈传递参数,用寄存器 eax 传递返回值;参数入栈的顺序是从右向左,父函数负责清理栈;并且规定,函数名称以前缀下划线“_”开头。——看到没,这是  C 语言采用的函数调用约定(cdecl )规定的!其他编程语言要想和 C 语言函数兼容,也必须遵守这个约定,不然 C 语言不认你!

    不过我们在编写 C 语言代码的时候没用下划线前缀啊?调用库函数的时候也不用在函数名前加下划线啊?那是因为 C 语言编译器在编译的时候都自动帮你加上了!而编写 NASM 汇编代码的时候又必须加前缀下划线,是因为 NASM 编译器在编译的时候没有自动帮你加前缀下划线,你得自己手工加上,以符合 cdecl 约定,才能被 C 语言函数识别。如果你编写的汇编语言函数不和 C 语言编写的函数产生关系,当然就可以不加前缀下划线了(废话!)。

    再顺便扩展下吧,除了 cdecl 约定,当然还有别的约定,比如 Pascal 约定:参数入栈的顺序是从左向右、子函数清栈,Pascal 约定刚好和 cdecl 约定相反。再比如还有折中的 stdcll 约定:参数入栈的顺序是从右向左(cdecl 约定),子函数负责清理栈(Pascal 约定)。

    那么 cdecl 约定有什么优缺点呢?

    缺点很明显:子函数负责清栈的话,不管这个子函数被调用了多少次,清栈代码只有一份;而父函数负责清栈的话,子函数被调用多少次,就要产生多少份清栈代码。所以 cdecl 约定的执行代码体积要膨胀。另外,只使用 eax 寄存器传送返回值,导致函数只有一个正式的返回值(用指针从参数传送的另算),而像 lua 语言等用堆栈传送返回值的可以有多个返回值。

    优点也很明显:参数从右向左入栈,最后一个入栈(即栈顶)的参数必然是参数表上最左面的那个参数。栈顶这个参数(即参数表上最左面这个参数)是可以立即访问的,只要它说明后面的参数个数、类型,那么子函数就可以完全明白其他参数在栈中的位置,就都可以访问了。那么在定义函数的时候,就可以不固定参数个数了——参数表右面用省略号,表示具体调用前不知道有些什么参数。只要在调用它的时候,参数表上最左面那个参数交代清楚它右边的参数个数和类型(就是把省略号交代清楚),就能准确无误的访问全部参数了。这就是大名鼎鼎的可变参数函数!

    正是由于可变参数函数不知道它自己在被调用的时候要来几个参数,所以它也没法清栈。只有调用它的父函数知道有几个参数,所以就必须由父函数清栈。


转载于:https://my.oschina.net/u/580100/blog/525925

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值