可變參數, printf 實現的原理 ,va_start和va_end

對於可變參數的函數可以,使用下面的宏來,獲取輸入的每一個參數
這些宏定義在stdarg.h中

  • typedef char *va_list;

  • va_start宏,獲取可變參數列表的第一個參數的地址(list是類型爲va_list的指針,param1是可變參數最左邊的參數):

    #define va_start(list,param1) ( list = (va_list)&param1+ sizeof(param1) )

  • va_arg宏,獲取可變參數的當前參數,返回指定類型並將指針指向下一參數(mode參數描述了當前參數的類型):

    #define va_arg(list,mode) ( (mode *) ( list += sizeof(mode) ) )[-1]

  • va_end宏,清空va_list可變參數列表:

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

由於調用函數之前,會把參數從右到左依次壓入棧中,所以只要確定一個參數的位置,其他的位置就可以找到
例如定義:fun(int n, …) 通過 fun(3,a, b, c)調用
如下圖會被壓入棧,確定3的位置,每次調用va_arg, ap指針上移,並把棧裏的值取出
在這裏插入圖片描述

#include<stdio.h>
#include<stdarg.h>

int fun(int num_args, ...)
{
        int val = 0;
        va_list ap;
        int i;

        va_start(ap,num_args);
        for(i = 0; i < num_args; i++)
        {
                val = va_arg(ap, unsigned);
                printf("%d\n", val);
        }
        va_end(ap);
        return val;
}

int main()
{
        fun(3, 10, 20, 30);
}

結果

kayshi@ubuntu:~/code/Test$ ./a.out 
10
20
30

一:直接利用地址指針的變化把每個參數打印出來

#include<stdio.h>
  
struct person{
        char *name;
        int age;
        char socre;
};

int put_test(const char* format, ...)
{
        char *p = (char *)&format;//讓p指向字符串format
        int i;
        struct person per;
        char c;
        double d;
        p = p + sizeof(char *);//p加一個指針的大小指向下一個元素的位置
        i = *((int *)p);//取出整形數據
        printf("arg1: %s\n", format);
        printf("arg1: %d\n", i);
        p = p + sizeof(int);//p加上整形數據的大小,指向下一個元素
        per = *((struct person *)p);//取出結構體,給per
        printf("arg3: .name = %s, age = %d, socre = %c\n", per.name, per.age, per.socre);
        p = p + sizeof(struct person);//p加上結構體的大小,指向下一個元素
        c = *((char *)p);//取出p指向的字符
        printf("arg4: %c\n", c);
        p = p + sizeof(char) + 3;//讓p加上字符的大小,在加上3,爲了字節對齊
        d = *((double *)p);//取出double類型的數據
        printf("arg5: %f\n", d);
        return 0;
}

int main()
{
        struct person per = {"kayshi", 29, 'A'};
        put_test("abcd", 123, per,'k', 2.79);

}

-m32 表示:在64位上的ubantu上以32位進行編譯

kayshi@ubuntu:~/code/put_test$ gcc -m32 put_test.c 
kayshi@ubuntu:~/code/put_test$ ./a.out 
arg1: abcd
arg1: 123
arg3: .name = kayshi, age = 29, socre = A
arg4: k
arg5: 2.790000

二:使用頭文件<stdarg.h>提供的宏來進行打印參數

#include<stdio.h>
#include<stdarg.h>

struct person{
        char *name;
        int age;
        char socre;
};

int put_test(const char* format, ...)
{
        int i;
        va_list p;
        struct person per;
        char c;
        double d;
        va_start(p, format);//p會執行format的下一元素
        i = va_arg(p, int);//取出p指向位置的值(整形),p加一個整數據的大小
        printf("arg1: %s\n", format);
        printf("arg1: %d\n", i);
        per = va_arg(p, struct person);//取出p指向位置的值(結構體),p加一個結構體的大小
        printf("arg3: .name = %s, age = %d, socre = %c\n", per.name, per.age, per.socre);
        c = va_arg(p, int);//取出p指向位置的值(char),p加一整形的大小(字節對齊)
        printf("arg4: %c\n", c);
        d = va_arg(p, double);//取出p指向位置的值(double),p加一double類型的大小
        printf("arg5: %f\n", d);
        va_end(p);
        return 0;
}

int main()
{
        struct person per = {"kayshi", 29, 'A'};
        put_test("abcd", 123, per,'k', 2.79);

}

kayshi@ubuntu:~/code/put_test$ gcc -m32 put_test1.c 
kayshi@ubuntu:~/code/put_test$ ./a.out 
arg1: abcd
arg1: 123
arg3: .name = kayshi, age = 29, socre = A
arg4: k
arg5: 2.790000
kayshi@ubuntu:~/code/put_test$ 

三:把頭文件中的宏定義提取到文件中,並把頭文件註釋

這是在stdarg.h中定義的宏
typedef char* va_list; va_list是char *

下面這句是爲了字節對齊,當sizeof(n)=1/2/4時,那麼_INTSIZEOF(n) = 4
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) -1) & ~(sizeof(int) -1))
下面這句表示,指針ap指向了字符串v的下一個位置
#define va_start(ap, v) (ap = (va_list)&v + _INTSIZEOF(v))
下面3句:取出指針ap指向的內容,並把指針ap在加本次佔的大小,就會指向下一個元素
第一句時頭文件本身的語句,不好理解。 後面兩句也可以實現同樣的功能
#define va_arg(ap, t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define va_arg(ap, t) (ap = ap + _INTSIZEOF(t), *(t*)(ap - _INTSIZEOF(t)))
#define va_arg(ap, t) (*(t*)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t)))
讓指針ap指向null,避免野指針
#define va_end(ap) (ap = (va_list)0)
#include<stdio.h>
//#include<stdarg.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_arg(ap, t) (ap = ap + _INTSIZEOF(t), *(t*)(ap - _INTSIZEOF(t)))
#define va_arg(ap, t) (*(t*)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t)))
#define va_end(ap) (ap = (va_list)0)

struct person{
        char *name;
        int age;
        char socre;
};

int put_test(const char* format, ...)
{
        int i;
        va_list p;
        struct person per;
        char c;
        double d;
        va_start(p, format);
        i = va_arg(p, int);
        printf("arg1: %s\n", format);
        printf("arg1: %d\n", i);
        per = va_arg(p, struct person);
        printf("arg3: .name = %s, age = %d, socre = %c\n", per.name, per.age, per.socre);
        c = va_arg(p, int);
        printf("arg4: %c\n", c);
        d = va_arg(p, double);
        printf("arg5: %f\n", d);
        va_end(p);
        return 0;
}
int main()
{
        struct person per = {"kayshi", 29, 'A'};
        put_test("abcd", 123, per,'k', 2.79);

}
kayshi@ubuntu:~/code/put_test$ gcc -m32 put_test1.c 
kayshi@ubuntu:~/code/put_test$ ./a.out 
arg1: abcd
arg1: 123
arg3: .name = kayshi, age = 29, socre = A
arg4: k
arg5: 2.790000

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章