• 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 <stdio.h>
    #include <string.h>
    #include <stdarg.h>
    #include <stdlib.h>
    
    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.
    
  • 相关阅读:
    她又在这个星期联系我了 20110422
    用心去做 20110307
    2011年随笔记
    让我有勇气坚持下去 20110427
    2011年随笔记 5月30号以后的日志薄
    因为迷失,所以 201103015
    迷失只是暂时 20110313
    我们做一对好哥们吧 20101228
    3.20号,她恢复了联系 20110320
    FW: Deploy reporting services web parts (RSWebParts) to SharePoint 2010
  • 原文地址:https://www.cnblogs.com/GyForever1004/p/8453197.html
Copyright © 2020-2023  润新知