C++ 学习 --- 变长参数(stdarg.h)的实现原理
引用
C++ 中对 stdarg.h 头文件进行了封装,该头文件实现了函数变长参数,能够在定义函数时不必完全指定参数个数,而编译器能够在代码编译时,拿到所有的参数,并进行相应的处理。
stdarg.h 中定义了 va_list 类型,va_start/va_arg/va_end/va_copy4 个宏,我们具体探究一下其实现原理。
变长参数定义与原理
定义
变长参数是 C 语言中的特殊参数形式,函数声明如下:
int printf(const char* format, ...)
这样的声明表示,该函数除了第一个参数类型为 const char*之外,后面可以追加任意数量和类型的参数;
在函数实现的过程中,我们使用 stdarg.h 中的宏来依次访问后续额外的参数。
使用方式
假设变长参数函数的最后一个具名参数(如上面的 format)为 lastarg,那么我们需要在函数实现中定义 va_list 类型的变量如下:
valist ap;
该变量后续将会指向每一个未知参数,首先我们需要使用 va_start 对 ap 进行初始化,注意,这里会用到 lastarg:
va_start(ap, lastarg);
然后,我们使用 va_arg 来依次获取下一个不确定的参数,假设该参数的类型为 T:
T next = va_arg(ap, T);
在函数结束之前,我们使用 va_end 清理现场:
va_end(ap)
实现原理
C 语言中默认的 cdecl 调用惯例是自右往左压栈传递函数参数,例如函数(后面跟着 count 个整数,计算它们的和)
int sum(int count,...);
那么对应调用sum(3,1,2,5)
,此时,在栈上面地址由低到高是 3,1,2,5,这样,我们知道了第一个参数 count 的地址,那么也能够依次获知上面三个参数的地址,最后计算出结果:
但是,实际上的过程中,后续每一个参数的类型都不一样,我们需要增加的地址数也不一样,所以我们需要进行改进,使用 void 或者 char 指针替代明确的指针 int*。
va_list 是一个指针,选择 void 或 char;
va_start 将 va_list 指向最后一个具名参数后面的位置,即第一个不确定参数的位置;
va_arg 获取当前参数的值,同时将指针指向下一个参数;
va_end 将指针置为 0。
所以,我们的宏这样定义:
注意:
va_start 中获取到 arg 地址后还要再加 sizeof(arg)才是第一个不确定参数的位置;
va_arg 中先将 ap+=sizeof(t),然后再减去 sizeof(t)得到当前的参数,但不影响 ap 的递增。
例子如下:
版权声明: 本文为 InfoQ 作者【桑榆】的原创文章。
原文链接:【http://xie.infoq.cn/article/af8d851c12035a63954c53d01】。文章转载请联系作者。
评论