可变参数的实现原理

可变参数

  1. 参数个数可变
  2. 参数类型可变

原理概述

由于在函数调用时,参数通过栈进行传递(stdcall、cdecl)而且参数入栈时会按照顺序依次入栈。
所以在被调用的函数内,知道了第一个参数的地址就可以根据类型获得其他参数的地址从而获取参数的值.
这里需要注意的一点:
上面提到的stdcall和cdecl是说明这两种参数都是通过栈传递,但是只有cdecl支持可变参数,因为cdecl由调用者维护栈平衡
另外,可变参数的限制有两个:

  1. 必须知道第一个参数的地址,也就是说函数内不能全部都是可变参数。可以把第一个参数设置为可变参数的数量,因为可变参数类型可变,个数可变,并不能规定一个通用的结束标志。例如printf这样的函数,都是根据第一个字符串内的%d这样占位符的数量确定参数个数,所以后面的可变参数不匹配就会出错甚至崩溃
  2. 必须知道每个参数的类型,因为不知道参数确切的类型,即使参数排列在连续的内存区域但是仍然无法知道每个参数的起始地址。这也是printf需要使用%d,%f这样来区分类型的原因

具体实现

头文件:stdarg.h
用到的宏:

功能 参数说明
va_list 可变参数类型,实际上是一个char* 指针
va_start 初始化va_list 传入va_list对象和起始参数
va_arg 获取下一个可变参数 va_list对象和下一个可变参数的类型
va_end 清理va_list 传入va_list对象

va_list

实际上就是,typedef char* va_list,由于可变参数类型不确定,只能通过设定最小单位的指针

va_start

#define va_start(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))))

这里ap就是va_list,x是第一个参数,_ASSRESSOF获取到第一个参数的地址,然后加上第一个参数占用的空间,这样va_list就指向可变参数第一个参数的地址
这里的_INTSIZEOF宏是为了确保内存对齐

va_arg

#define va_arg(ap, t)     (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

va_arg的ap是一个va_list,t是可变参数的类型,通过将ap加上该参数占用的空间,可将va_list指向下一个参数地址
这里先让va_list指向下一个地址,然后减去空间占用返回当前参数

va_end

就是将va_list也就是指针,赋nullptr

_INTSIZEOF

该宏是为了进行字节对齐,在x86平台下使用4字节对齐,而切换到x64编译将进行其他的实现,也就是8字节对齐了,这里看x86下的实现
(实际上在不同的平台下,va系列的宏是有不同的实现的,但是本质都差不多)

#define _INTSIZEOF(n)          ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))

Why need this

如果第一个参数是char类型,简单的加上sizeof(char)是不对的,因为考虑到字节对齐,即使第一个参数实际占用1个字节,也会拓展3个字节保证内存访问的效率。所以通过sizeof(t)获取的必须向4的整数倍对齐并向上取整,这个宏的功能就是这样
例如:

  • 1字节占用 -> 4字节
  • 2字节占用 -> 4字节
  • 3字节占用 -> 4字节
  • 4字节占用 -> 4字节

How implement

使用整除运算符可以进行向下取整,为了能进行向上取整可以通过判断余数是否为0来进行整除结果的+1操作
但是更快的方法可以用过增大分子的数值,这个数值必须满足下列条件:
(r => 余数, N为整除分母)

  • r + x >= N
  • x < N

得到x == N - 1
所以sizeof(n)必须加上sizeof(N) - 1,得到:
(((sizeof(n) + sizeof(int) - 1)) / (sizeof(int))) * sizeof(int)
这里的/是整除,如果N是2的整次幂,可以使用移位运算提高效率
(n + int - 1) >> m << m
后面部分实际上就是将(n + int - 1)的后面m位置0,所以相当于与上一个bit Mask,这个bit mask的后m位是0,其余为0最后就是_INTSIZEOF的定义
非常巧妙,一点一点在效率上提升

参考链接

https://blog.csdn.net/u010476094/article/details/39527697 解释_INTSIZEOF
https://blog.csdn.net/morewindows/article/details/6707662 可变参数入门
https://www.cnblogs.com/weiym/archive/2012/09/18/2689917.html 可变参数原理,里面的结构体如何传递有一点启发

你可能感兴趣的:(C++)