变参函数的详细分析

我们利用printf函数来搞懂变参函数,我们知道printf函数的原型为

int printf(const char *format, ...);

其中...代表了变参,我们通过printf函数入手来确定固定参数,可变参数,最后引出变参函数。

1.确定固定参数参数

固定参数是一个char* 的指针,我们可以将它所指的内容直接打印出来

#include <stdio.h>

int MyPrintf(const char *format, ...)
{
    printf ("Arg1 = %s\n", format);

    return 0;
}

int main(int argc, char **argv)
{
    MyPrintf("format");
    return 0;
}

首先利用上面的代码将固定参数打印出来,运行结果可以看到运行结果如下

2.手工确定可变参数

确定可变参数主要根据函数传递参数时,参数从左到右依次入栈。

可变参数会放在format这个指针变量的后面,因此我们定义一个指针来指向固定参数,然后移动指针指向可变参数,将指针指向的值打印出来

#include <stdio.h>

int MyPrintf(const char *format, ...)
{
    int iArg2;
    char *p = (char *)&format;
    
    printf ("Arg1 = %s\n", format);

    p = p + sizeof (char *);
    iArg2 = (*(int *)p);
    printf ("Arg2 = %d\n", iArg2);

    return 0;
}

int main(int argc, char **argv)
{
    //MyPrintf("format");
    MyPrintf("format", 123);
    return 0;
}

运行结果如下,成功的将可变参数打印出来了


继续修改代码,增加不同类型的可变参数

#include <stdio.h>

int MyPrintf(const char *format, ...)
{
    int iArg2, iArg4;
    char cArg3;
    char *p = (char *)&format;
    
    printf ("Arg1 = %s\n", format);

    p = p + sizeof (char *);
    iArg2 = (*(int *)p);
    printf ("Arg2 = %d\n", iArg2);

    p = p + sizeof (int);
    cArg3 = (*(char *)p);
    printf ("Arg23 = %c\n", cArg3);

    p = p + sizeof (char);
    iArg4 = (*(int *)p);
    printf ("Arg4 = %d\n", iArg4);

    return 0;
}

int main(int argc, char **argv)
{
    //MyPrintf("format");
    //MyPrintf("format", 123);
    MyPrintf("format", 123, 'A', 456);
    |return 0;
}

我们添加了字符‘A’和后面的一个整数456,运行结果如下并没有打印出我们期望的456


这是因为需要4字节对齐,我们在输出字符之后移动指针的时候移动了一个字节,但是其实'A'字符后面的三个字节是空的,为了4字节对齐,因此需要修改代码如下, +3 & ~3 即可4字节对齐,对于1/2/4字节都可,有兴趣可以去验证一下。


继续修改代码

#include <stdio.h>

int MyPrintf(const char *format, ...)
{
    int iArg2, iArg4;
    char cArg3;
    double dArg5;
    char *p = (char *)&format;
    
    printf ("Arg1 = %s\n", format);

    p = p + sizeof (char *);
    iArg2 = (*(int *)p);
    printf ("Arg2 = %d\n", iArg2);

    p = p + sizeof (int);
    cArg3 = (*(char *)p);
    printf ("Arg23 = %c\n", cArg3);

    p = p + (sizeof (char) + 3 & ~3);
    iArg4 = (*(int *)p);
    printf ("Arg4 = %d\n", iArg4);

    p = p + sizeof (int);
    dArg5 = (*(double *)p);
    printf ("dArg5 = %f\n", dArg5);

    return 0;
}

int main(int argc, char **argv)
{
    //MyPrintf("format");
    //MyPrintf("format", 123);
    //MyPrintf("format", 123, 'A', 456);
    MyPrintf("format", 123, 'A', 456, 2.33);
    return 0;
}

添加一个小数,运行结果如下,能够成打印出来,



但是如果将代码中的double修改为float得到的结果如下

这说明小数在printf里面是以double类型存放的。

2.  自动确定可变参数

根据上面手工确定可变参数我们可以确定几个步骤

1.首先将指针指向第一个可变参数

2. a.取值

b.指针移动到下一个可变参数的地方

3.重复a,b

4.最后将指针清为NULL,避免野指针的出现


因此,可变参数就主要有了变参函数


va_list ap   /* 就等价于char *ap,定义一个指针 */

va_start(ap, format)    /* 就是将指针ap移动到第一个可变参数的地方,即ap = ap + sizeif(char *) */

va_arg(ap, 变量类型)     /* 返回当前ap指针指向的值,然后指向下一个可变参数 */

va_end                   /* 将指针请为NULL */


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)    ap += _INTSIZEOF(t), *(*t)(ap - _INTSIZEOF(t))
#define va_end(ap)      ( ap = (va_list)0 )


可以看到va_list就是char *, _INTSIZEOF(n)就是将地址4字节对齐,就是 + 3 & ~3

其中va_arg可能比较难理解,我们重新定义一下va_arg

由于va_arg要实现两个功能,

1.取值 2. 移动指针  因此我们考虑用逗号表达式来实现

表达式1,表达式2             逗号表达式依次执行表达式1,2,然后将表达式2的值作为结果


因此    表达式1要移动指针到下一个变参   ap += _INTSIZEOF(t)

表达式2要取指针的值,但是指针已经移动到下一个变参了,因此我们需要先移动回来再取值

*(*t)(ap - _INTSIZEOF(t))

因此va_arg可以定义为 ap += _INTSIZEOF(t), *(t*)(ap - _INTSIZEOF(t)))


利用这几个变参函数实现同样的效果:

#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_arg(ap,t)    (ap = ap + _INTSIZEOF(t),*(t*)(ap - _INTSIZEOF(t)))
#define va_end(ap)      ( ap = (va_list)0 )



int MyPrintf(const char *format, ...)
{

    int iArg2, iArg4;
    char cArg3;
    double dArg5;
    //char *p = (char *)&format;
    va_list p;
    
    
    printf ("Arg1 = %s\n", format);
    
    //p = p + sizeof (char *);
    //iArg2 = (*(int *)p);
    va_start(p, format);
    iArg2 = va_arg(p, int);
    printf ("Arg2 = %d\n", iArg2);

    //p = p + sizeof (int);
    cArg3 = va_arg(p, char);
    printf ("Arg3 = %c\n", cArg3);

    //p = p + (sizeof (char) + 3 & ~3);
    iArg4 = va_arg(p, int);
    //iArg4 = (*(int *)p);
    printf ("Arg4 = %d\n", iArg4);

    //p = p + sizeof (int);
    dArg5 = va_arg(p, double);
    //dArg5 = (*(double *)p);
    printf ("dArg5 = %f\n", dArg5);

    va_end(p);

    return 0;
}

int main(int argc, char **argv)
{
    //MyPrintf("format");
    //MyPrintf("format", 123);
    //MyPrintf("format", 123, 'A', 456);
    MyPrintf("format", 123, 'A', 456, 2.33);
    return 0;
}








  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值