下面主要对上面几行代码分析和自己实现。
该行主要就是定义一个char类型的指针。(为什么是char不是其它类型呢?)
答:主要原因是char类型的指针进行加法运算,每次增加是以1个字节为基础,int*的类型每次加1实际是增加4个字节。所以char类型的指针更灵活操作。
这两行是平台相关的
其主要为下面两种实现方式
typedef s32 acpi_native_int 若采用这种宏定义,表明int 类型是用32位表示,也表示当前内存是4字节对齐
typedef s64 acpi_native_int 若采用这种宏定义,表明int 类型是用64位表示,也表示当前内存是8字节对齐
本文以4字节对齐来进行讲解。
带入上式后即可得到该宏的表示
3用2进制表示为 11b ,即低两位是1
sizeof (X)通常得到的是一个类型的字节数,通常为1,4,8....
把1,4,8带入上式可以发现其结果为
#define _bnd(char,3) ==> (1+3)&(~3) ==> 4
#define _bnd(int,3) ==> (4+3)&(~3) ==> 4
#define _bnd(double,3) ==> (4+3)&(~3) ==> 8
........
可以发现它都是4的整数倍,同时满足传入类型无论是否为4(sizeof(int)),经过#defeine _bnd后都是4字节向上对齐
其作用也是参数传递过过程中对齐传递(比如char类型,传递参数时若入栈是4字节对齐压栈的)
接下来说一下C语言中函数传参当参数个数小于4个(也可能其他,和编译器有关),会通过寄存器传过去比如r0,r1,r2,r3
进入函数内部后,这几个寄存器还是要压栈(因为函数中要使用寄存器)。
其压栈顺序和参数传入顺序相反比如:下面xxx函数是先对参数c压栈后对参数b,最后压栈参数a.
void xxx(int a,int b,int c)
因为ARM平台默认是满减栈,所以参数c存在高地址,参数a存在低地址。
当参数个数超过4个(也可能是其他)时,其他参数也是由后往前压栈,前4个仍然使用寄存器传入后压栈。
假设有五个参数,void xxx(int a,int b,int c,int d,int e),其最终在内存中的排布如下
若是知道a的地址,知道每个参数的类型,则可以通过指针得到每一个参数的值。
接下来引入可变参函数,以printf为例
下面是printf函数的原型
int printf(const char *format, ...);
在printf函数中,知道了第一个参数的地址(通常第一个参数都为指针类型),则可以通过%d,%s,%x,%f之类得到参数类型,进而通过指针运算找到对应参数。
对下面这个printf的使用为例子来分析
printf (" %s .%d..%lf ", p_char,int_i,double_d);
下面根据上面的分析,画出,四个参数在内存中的分布图。
接下来主要分析可变参数中用到的宏(即如何通过指针运算找到真确地址)
va_start(ap, A)有两个参数,一个是函数传入的第一个参数(比如printf中的format),必须有这个参数,才能进行后面的数据索引
ap为用户自定义的一个参数通常用系统给定的va_list来定义 表示为va_list ap ==> char * ap
该宏为初始化ap,同时得到下一个参数的地址。
带入p参数
因为format为一个指针类型,32位平台为4个字节,所以后半部分的表达式经过对齐运算后_bnd (format,3)为4
即 ap = (void)((char*)&(format) + 4) 恰好format指针为四个字节,接下来ap指向p_char的字符指针类型。
而取出字符串的方法也很简单,使用下面这个宏即可实现。
这个宏有两个参数,一个是字符指针类型ap,另一个为T类型,即要在ap这个起始地址取出的数据的类型。
因为第一个参数中的%s以及提示第二个参数为字符指针类型。
则最简单的方法就是 把char*类型的指针ap转换为 T类型的指针ap,然后进行引用操作
转换为T类型的指针可以这样操作(T*)ap
然后取出这个值就很简单了*((T*)ap )
但va_arg这个宏还要满足取值的值返回,同时ap指向下一个参数的地址,所以一条语句满足两个结果就稍微麻烦一些。
最简单的方法是使用逗号表达式,一条语句满足实现两种功能,并返回逗号前面的值
比下面这种实现:
逗号前面的是取值,逗号后面的是让ap指向下一个参数的地址(其实就是va_start)
而内核使用了一种比较花哨的写法
先让ap 加上下一个参数的偏移量,然后又减去这个偏移量。然后得到对应值。
标红的为让ap指向下一个参数的地址。但后面减去偏移量的值不用ap保存接收,而是直接通过类型转换,得到值。
起始和用逗号表达式的效果一致。但唯一对使用者来说看起来不是那么容易理解,但可能对库本身的实现的那类人来说,应该和逗号表达式的看起来 难度是一样的。