第三课,变参函数的详细分析

注:以下内容学习于韦东山老师arm裸机第一期视频教程


一.变参函数分析


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

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

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

    1.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;
    }

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



    1.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;
    }
    运行结果如下,成功的将可变参数打印出来了

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

        #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字节都可,有兴趣可以去验证一下。


        1.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.2  a.取值


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


    2.3 重复a,b


    2.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;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值