C 语言可变参数实现原理总结

1. 代码

#include <stdio.h>

typedef char * va_list;

#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1)  &  ~(sizeof(int)-1))
#define va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v))
#define va_arg(ap, t) ( *( t * ) ((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap)  (ap = (va_list)0)

void mprintf(char *, ...);

int main()
{
    int i;
    char *s;

    mprintf("test start...\n");

    i = 1;
    s = "hello world!";
    mprintf("i=%d, s=%s\n", i, s);

    s = "second test!";
    mprintf("s=%s\n", s);
}

void mprintf(char *fmt, ...)
{
    char *p, *sval;
    int ival;

    va_list ap;

    va_start(ap, fmt);

    for (p=fmt; *p; p++)
    {
        if (*p != '%')
        {
            putchar(*p);
            continue;
        }

        switch (*++p)
        {
            case 'd':
                ival = va_arg(ap, int);
                printf("%d", ival);
                break;

            case 's':
                for ( sval=va_arg(ap, char *); *sval; *sval++)
                    putchar(*sval);
                break;

            default:
                putchar(*p);
                break;
        }
    }

    va_end(ap);
}
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

2. 宏定义 _INTSIZEOF(n)

代码:

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

作用:

将变量 n 的长度向上对齐为 int 类型长度的整数倍.

即:
如果 sizeof(int) = 4; sizeof(n) = 2, 则 _INTSIZEOF(n) = 4;
如果 sizeof(int) = 4; sizeof(n) = 6, 则 _INTSIZEOF(n) = 8;

sizeof()函数:

sizeof()函数返回变量(如: n),或变量类型(如: int)在内存中所占的字节(byte)数.

运算原理:

设 sizeof(int) = INT, sizeof(n) = N. 则: INT, N 皆为正整数.

则存在正整数 q, 非正整数 r, 使得:
    N = (INT * q) + r

其中:
    * : 表示相乘
    r : -INT < r <= 0 (最大非正剩余)

所谓 N 按 INT 向上对齐, 就是取 INT * q

为了便于求得 INT * q 的值, 需要将 r 变为 最小非负剩余, 等号两边加 (INT - 1), 则:
    N + INT - 1 = (INT * q) + (r + INT - 1)

此时:
    -1 < r + INT - 1 <= INT - 1

等价于:
    0 <= r + INT - 1 < INT (即: r + INT - 1 为最小非负剩余)

那么, 根据运算法则, INT * q =  [(N + INT - 1)/INT] * INT  ([]为取整)

INT 的值在系统中为 2 的方幂, 设为 INT = 2^m, 则除 INT, 即为二进制右移 m 位. 乘为左移 m 位. 此处先除 INT, 再取整, 再乘 INT, 相当于将最后m 个二进制位清零.

故:
    INT - 1 相当于二进制 最后 m 位全部为 1, 其他为 0
    ~(INT - 1) 相当于二进制最后 m 位全部为 0, 其他为 1
    (N+INT-1) & ( ~(INT-1) ) 相当于将 (N+INT-1) 的最后 m 位全部清零. 其他不变.

从而:
    (N + INT - 1) & ( (~(INT-1)) ) 即为所求的对齐后的值.

对齐

  • 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

  • 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况, 但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为 32位)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低 字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。

  • 参考 http://www.189works.com/article-10322-1.html

3. 宏定义 va_start(ap, v)

代码:

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

作用:

将 ap 赋值为指向函数的第一个参数之后的下一个参数(即第一个可变参数)的地址.

详解:

v 为函数的第一个参数。如本例子中的: char * fmt

(va_list)&v => 变量 v 的地址, 并将此地址的指针类型转为(va_list)类型.

_INTSIZEOF(v) => 变量 v 的对齐后的长度.

函数的可变参数以堆栈的方式存储. 故此处将 ap 赋值为指向函数的第一个参数之后的下一个参数(即第一个可变参数)的地址.

4. 宏定义 va_arg(ap, v)

代码:

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

作用:

返回 ap 所指向的可变参数的值 (类型为 t), 然后将 ap 指向下一个可变参数首地址.

详解:

(ap += _INTSIZEOF(t)) 
    ap 所指的地址增加类型 t 的对齐后的长度. 即指向下一个可变参数.

((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))
    ap 虽改变, 但仍需返回原 ap 所指向的值. 故再减掉  _INTSIZEOF(t)

(t *)
    将指针类型转换为 t

* (t *)
    取出转换后的指针所指向的值.

5. 宏定义 va_end(ap)

代码:

#define va_end(ap)  (ap = (va_list)0)

作用:

将指针 ap 置为 va_list 类型的空指针.

6. 参考文档

最近更新: 10/7/2018, 7:49:52 PM