C-----可变参数

来源:互联网 发布:网络家长学校 编辑:程序博客网 时间:2024/06/13 10:49
6. 可变参数
到目前为止我们只见过一个带有可变参数的函数printf:
int printf(const char *format, ...);
以后还会见到更多这样的函数。现在我们实现一个简单的myprintf函数:
例 24.9. 用可变参数实现简单的printf函数



这些参数是从右向左依次压栈的,所以第一个参数靠近栈顶,第三个参数靠近栈底。这些参数在内存中是连续存放的,每个参数都对齐到4字节边界。第一个和第三个参数都是指针类型,各占4个字节,虽然第二个参数只占一个字节,但为了使第三个参数对齐到4字节边界,所以第二个参数也占4个字节。现在给出一个stdarg.h的简单实现,这个实现出自[
Standard C Library]:

例 24.10. stdarg.h的一种实现



     这个头文件中的内部宏定义_Bnd(X, bnd)将类型或变量X的长度对齐到bnd+1字节的整数倍,例如_Bnd(char, 3U)的值是4,_Bnd(int, 3U)也是4。
在myprintf中定义的va_list ap;其实是一个指针,va_start(ap, format)使ap指向format参数的下一个参数,也就是指向上图中esp+4的位置。然后va_arg(ap, int)把第二个参数的值按int型取出来,同时使ap指向第三个参数,也就是指向上图中esp+8的位置。然后va_arg(ap,char *)把第三个参数的值按char *型取出来,同时使ap指向更高的地址。va_end(ap)在我们的简单实现中不起任何作用,在有些实现中可能会把ap改写成无效值,C标准要求在函数返回前调
用va_end。
如果把myprintf中的char ch = va_arg(ap, int);改成char ch = va_arg(ap, char);,用我们这个stdarg.h的简单实现是没有问题的。但如果改用libc提供的stdarg.h,在编译时会报错:

        因此要求char型的可变参数必须按int型来取,这是为了与C标准一致,我们在第 3.1 节“Integer Promotion”讲过Default Argument Promotion规则,传递char型的可变参数时要提升为int型。
从myprintf的例子可以理解printf的实现原理,printf函数根据第一个参数(格式化字符串)来确定后面有几个参数,分别是什么类型。保证参数的类型、个数与格式化字符串的描述相匹配是调用者的责任,实现者只管按格式化字符串的描述从栈上取数据,如果调用者传递的参数类型或个数不正确,实现者是没有办法避免错误的。
还有一种方法可以确定可变参数的个数,就是在参数列表的末尾传一个Sentinel,例如NULL。execl(3)就采用这种方法确定参数的个数。下面实现一个printlist函数,可以打印若干个传入的字符串。
例 24.11. 根据Sentinel判断可变参数的个数

printlist的第一个参数begin的值并没有用到,但是C语言规定至少要定义一个有名字的参数,因为va_start宏要用到参数列表中最后一个有名字的参数,从它的地址开始找可变参数的位置。实现者应该在文档中说明参数列表必须以NULL结尾,如果调用者不遵守这个约定,实现者
是没有办法避免错误的。
原创粉丝点击