C编译器剖析_C语言的变参函数

C语言的变参函数

    UCC编译器中有不少地方使用了C语言的变参函数,这里我们专门用一小节来对C语言变参函数的实现原理进行分析。C标准库中的printf函数就是一个典型的变参函数,其接口如下所示,函数声明中的省略号…表明这是一个变参函数。

         int printf(const char *format, ...);

    下面我们举一个简单的例子来说明printf函数的调用过程,如图4.2.12所示。图中第1至11行对应的是hello.c,而第12至25行是由UCC编译器生成的抽象语法树hello.ast,第26至33行则是UCC产生的中间代码hello.uil,第34至52行则是UCC生成的部分汇编代码hello.s。


图4.2.12 printf()函数

     我们注意到第9行的实参a是float类型,而d是char类型,第17行的抽象语法树的功能是把实参a从float转换成double类型,而第21行则用于把实参d从char转换成int类型。我们在2.4节讨论C语言的类型系统时介绍过,对于形如int f()的旧式风格函数声明,C编译器会按C标准的要求进行实参提升的操作,在UCC编译器中,这个动作由函数PromoteArgument()来完成。对于变参函数中的“无名”参数,例如上述第9行printf函数调用中格式化字符串之后从a开始的参数,这些参数在函数printf的声明中并没有与之对应的形参名。按C标准的要求,C编译器也要对这些无名参数进行实参提升,即把小于int型的char和short提升为int类型,把float类型提升为double类型。图4.2.12第29至31行的中间代码很直观地反映了这个实参提升的过程。与之对应的汇编代码如第39至52行所示,第39至40行的代码把float类型的a转换为double类型,把转换后的结果存到临时变量-12(%ebp)中,我们在1.5节时介绍过与浮点运算相关的汇编指令。按照C函数的调用约定,参数按从右到左的次序依次入栈,第41行的汇编指令完成了把参数d由char到int的转换,第42行把转换后的结果入栈,第43行则把参数c入栈,第44至46行则从全局静态数据区加载双精度浮点数b并入栈,第47至49行则从临时变量-12(%ebp)中加载双精度浮点数,并入栈。第50行把格式化字符串的首地址入栈,我们在第38行时已将其地址存到寄存器eax中,第51行则进行真正的函数调用,因为所有的参数都是存放在栈中,共占去了4+8+8+4+4(即28)字节,当函数printf返回时,我们在第52行把esp指针进行加28的操作。

     库函数printf()的代码在我们编写上述hello.c时就已经存在,这意味着被调函数printf其实并不知道我们在调用它时,到底传递了几个实参。对printf而言,它只是按照格式化字符串的说明,从栈中取出相应的参数,如下所示:

         // 实际上只有10这一个参数,但printf看到有两个%d,

         // 于是仍试图从栈中取两个参数,打印出形如10,1074172310的垃圾值

         printf(“%d, %d “,10);       

         // 实际上有10,20,30这3个参数,但printf只看到一个%d,

         // 于是只打印出参数10

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值