翻看Masonry的源码, 跟踪 #define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__))) 的时候看到有这样的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
 /**
* Given a scalar or struct value, wraps it in NSValue
* Based on EXPObjectify: https://github.com/specta/expecta
*/
static inline id _MASBoxValue(const char *type, ...) {
va_list v;
va_start(v, type);
id obj = nil;
if (strcmp(type, @encode(id)) == 0) {
id actual = va_arg(v, id);
obj = actual;
} else if (strcmp(type, @encode(CGPoint)) == 0) {
CGPoint actual = (CGPoint)va_arg(v, CGPoint);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(CGSize)) == 0) {
CGSize actual = (CGSize)va_arg(v, CGSize);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
obj = [NSValue value:&actual withObjCType:type];
} else if (strcmp(type, @encode(double)) == 0) {
double actual = (double)va_arg(v, double);
obj = [NSNumber numberWithDouble:actual];
} else if (strcmp(type, @encode(float)) == 0) {
float actual = (float)va_arg(v, double);
obj = [NSNumber numberWithFloat:actual];
} else if (strcmp(type, @encode(int)) == 0) {
int actual = (int)va_arg(v, int);
obj = [NSNumber numberWithInt:actual];
} else if (strcmp(type, @encode(long)) == 0) {
long actual = (long)va_arg(v, long);
obj = [NSNumber numberWithLong:actual];
} else if (strcmp(type, @encode(long long)) == 0) {
long long actual = (long long)va_arg(v, long long);
obj = [NSNumber numberWithLongLong:actual];
} else if (strcmp(type, @encode(short)) == 0) {
short actual = (short)va_arg(v, int);
obj = [NSNumber numberWithShort:actual];
} else if (strcmp(type, @encode(char)) == 0) {
char actual = (char)va_arg(v, int);
obj = [NSNumber numberWithChar:actual];
} else if (strcmp(type, @encode(bool)) == 0) {
bool actual = (bool)va_arg(v, int);
obj = [NSNumber numberWithBool:actual];
} else if (strcmp(type, @encode(unsigned char)) == 0) {
unsigned char actual = (unsigned char)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedChar:actual];
} else if (strcmp(type, @encode(unsigned int)) == 0) {
unsigned int actual = (unsigned int)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedInt:actual];
} else if (strcmp(type, @encode(unsigned long)) == 0) {
unsigned long actual = (unsigned long)va_arg(v, unsigned long);
obj = [NSNumber numberWithUnsignedLong:actual];
} else if (strcmp(type, @encode(unsigned long long)) == 0) {
unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
obj = [NSNumber numberWithUnsignedLongLong:actual];
} else if (strcmp(type, @encode(unsigned short)) == 0) {
unsigned short actual = (unsigned short)va_arg(v, unsigned int);
obj = [NSNumber numberWithUnsignedShort:actual];
}
va_end(v);
return obj;
}

#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))

static inline id _MASBoxValue(const char *type, ...) 是个可变参数的内敛函数。

可变参数函数

可变参数是C语言的一个特性。va_XXX 这些函数操作的原理是:在进程中,堆栈地址是从高到低分配的.当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,一些黑客就是在堆栈中修改函数返回地址,执行自己的代码来达到执行自己插入的代码段的目的. 总而言之栈中的地址分布是这样的, 地址从高到低,依次是:函数参数列表,函数返回地址,被调用函数局部变量. 另外:最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分

计算Int内存占用大小的宏,#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

  • va_list
1
2
3
4
5
6
7
8
#ifdef  _M_ALPHA
typedef struct {
char *a0; /* pointer to first homed integer argument */
int offset; /* byte offset of next parameter */
} va_list;
#else
typedef char * va_list;
#endif
  • va_start
1
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //得到第一个可变参数地址,
  • va_arg
1
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址
  • va_end
1
#define va_end(ap)    ( ap = (va_list)0 )                            // 将指针置为无效

虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数,但是由于不知道可变参数有多少个,什么时候应该结束遍历,如果在堆栈中遍历太多,那么很可能读取一些无效的数据.
解决办法:可以在第一个起始参数中指定参数个数,那么就可以在循环还中读取所有的可变参数;b.定义一个结束标记,在调用函数的时候,在最后一个参数中传递这个标记,这样在遍历可变参数的时候,可以根据这个标记结束可变参数的遍历;

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

//第一个参数指定了参数的个数
int sum(int number,...)
{
va_list vaptr;
int i;
int sum = 0;
va_start(vaptr,number);
for(i=0; i<number;i++)
{
sum += va_arg(vaptr,int);
}
va_end(vaptr);
return sum;
}


int main()
{
printf("%d\n",sum(4,4,3,2,1));
system("pause");
return 0;
}

var_XXX 宏的理解

  • 内存对齐

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

先看看 ~(sizeof(int) - 1)的值, sizeof(int) 为4,减1为3,二进制表示为 11,取非,则变成为 00((sizeof(n)+sizeof(int)-1)& 与上 00。 也就是说 _INTSIZEOF(n)计算出来的值肯定是 4的倍数。 也就是说当sizeof(n)的结果在1~4之间是,_INTSIZEOF(n)的结果会是4;当sizeof(n)的结果在5~8时,_INTSIZEOF(n)的结果会是8...

  • va_start 得到第一个参数地址

宏:#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

va_list(第一个参数的最后一个字节的地址,也就是函数返回指针的地址减一) 可看成是 char * 类型,进行这样的指针转换是为了让 v地址+1的时候,地址指针就是+1,而不是+4(指针加法)。

  • va_arg ,获取可变参数的当前参数,

宏:( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

(ap += _INTSIZEOF(t) 使ap指向下一个参数的地址。 - _INTSIZEOF(t) 后再 *(t *) 获取 t类型的值