可变参数, 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指针上移,并把栈里的值取出
在这里插入图片描述

在32位系统中,可以这样。但是64位系统中这样使用不行

#include<stdio.h>

long sum(int i, ...)
{
        int *p, j;
        long s = 0;
        p = &i + 1;
        for(j = 0; j < i; j++)
                s += p[j];
        return s;
}

void main(void)
{
        long Sum=0;
        Sum = sum(3, 1, 2, 3);

        printf("%ld\n", Sum);
}

加上-m32 编译32位的执行程序。

kayshi@ubuntu:~/code/Test$ gcc -m32 variable_parameter.c 
kayshi@ubuntu:~/code/Test$ ./a.out 
6

在64位系统中可以使用标准库中的函数来实现

#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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值