C 语言精髓之变参函数

我们以 printf 这个 very 熟悉的函数为例,来分析一下变参函数。先看下 printf 函数的定义:

int printf(const char *fmt, ...){    int i;    int len;    /* va_list 即 char * */
    va_list args;
    
    va_start(args, fmt);    
    /* 内部使用了 va_arg() */
    len = vsprintf(g_PCOutBuf,fmt,args);
    
    va_end(args);    
    for (i = 0; i < strlen(g_PCOutBuf); i++)
    {
        putc(g_PCOutBuf[i]);
    }    
    return len;
}

什么是变参函数?

可变参数函数的原型声明为 type VAFunction(type arg1, type arg2, … );

参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用 "..." 表示。固定参数和可选参数共同构成一个函数的参数列表。


printf 函数涉及了以下几个重要的宏:

typedef char *   va_list;/*
 * Storage alignment properties
 */#define _AUPBND (sizeof (acpi_native_int) - 1) //acpi_native_int 为 4 字节(根据机器字长而定)#define _ADNBND (sizeof (acpi_native_int) - 1) /*
 * Variable argument list macro definitions
 */#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))#define va_end(ap) (void) 0#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

分析以下三个宏的作用


1) va_start 宏

作用: 根据 A 取得可变参数表的首指针并赋值给 ap。

原理: 根据最后一个固定参数 A 的地址 + 第一个变参对 A 的偏移地址,然后赋值给 ap,这样 ap 就是可变参数表的首地址(函数传递的参数会从右向左依次入栈,并且 ARM 的栈为降栈,所以参数 A 的地址最低)。


2) va_arg 宏

作用: 指取出当前 ap 所指的可变参数并将 ap 指针指向下一可变参数。


3) va_end 宏

作用: 结束可变参数的获取,与 va_start 对应使用。

堆栈中,各个函数的分布情况是倒序的,即最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分。参数在堆栈中的分布情况如下:

    *******************
       最后一个参数  
       倒数第二个参数 
       ...          
       第一个参数     
       函数返回地址   
       函数代码段     
    *******************

得到可变参数个数的三种办法:1) 函数的第一个参数,指定后续的参数个数,如 func(int num,...);2) 根据隐含参数,判断参数个数,如 printf 系列的,通过字符串中 % 的个数判断;3) 特殊情况下(如参数都是不大于 0xFFFF 的 int),可以一直向低处访问堆栈,直到返回地址。

而 _bnd(X, bnd) 宏就是以 4 字节对齐的变量 X 的大小。

自己实现一个简单的 printf 函数

#include #include #include #include void print(char *fmt, ...){
    va_list ptr;
    va_list temp_ptr = NULL;    /* 用于存储最终转换的结果 */
    char buf[100] = {0};    /* 用于存储临时转换的结果 */
    char temp_buf[50] = {0};    unsigned char index = 0;    unsigned char len, arg_len;

    len = strlen(fmt);    /* 得到可变参数的首地址 */
    va_start(ptr, fmt);    for(; *fmt; fmt++){        if(*fmt == '%'){            switch(*++fmt){                case 'd':                case 'D':                    sprintf(temp_buf, "%d", va_arg(ptr, int));
                    temp_ptr = temp_buf;                    break;                case 's':                case 'S':                    /* 取出当前变量,并将指针指向下一个可变参数 */
                    temp_ptr = va_arg(ptr, char*);                    break;
            }
            arg_len = strlen(temp_ptr);            strcat(buf+index, temp_ptr);
            index += arg_len;
        }else{
            buf[index] = *fmt;
            index++;
        }
    }    /* 结束取参 */
    va_end(ptr);    /* 输出最终转换结果 */
    puts(buf);
}int main(){
    print("My name is %s and my height is %d cm.", "Lance#", 178);    return 0;
}

程序运行结果:

My name is Lance# and my height is 178 cm.

声明:

本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值