可变参数的函数的原理

简介:

原文地址:

可变参数。
1:必须有一个提前参数,(即:...之前必须要有一个参数),用以计算出后面的第一个未知参数的地址. 知道了第一个未知参数的地址之后, 就可以根据fmt格式化串,可以依次计算出剩余的参数地址.
sprintf()的原型:sprintf(char* buffer, const char* fmt, ... ) ,其中,fmt就是提前参数
2:每一个可变阐述函数,其编写者与使用者 都要有一个参数的使用约定。不然,会乱套。

3:可变函数实现的技术基础
1:所有参数,在汇编级别,其大小都是4个字节的整数倍。char,short类型也被扩展成4byte
把任意长度的类型扩展成4byte的整数倍的技术就是 
_INTSIZEOF(n) ( (sizeof(n)+sizeof(int)-1) & ~(sizeof(int)-1))
简化就是,((sizeof(n)+4-1) & ~(4-1))
_INTSIZEOF(char*) = 4
_INTSIZEOF(2byte) = 4
_INTSIZEOF(double)= 8

2:有了上述的知识,下面就是,从可变参数中一个一个去参数了
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
               // :取得已知参数的地址,
               // ap 就是第一个未知参数地址,v就是上述的提前参数fmt
               // eg :fun( int n,...) v就是n的地址。


#define va_arg(ap,t) \
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )  

va_arg()替换后,ap指向t类型后的下一个类型的地址。va_arg本身被替换成了t类型对应参数的值. t类型一般是从fmt格式化串中根据%d,%c等转换的.
比如1: int a = va_art(ap, int);

比如2: fun(int n,...)
{
...
char* pChar = va_arg(ap, char*)
int tmp = va_arg(ap, int)
double dd = va_arg(ap, double)
}
那么,...必然是 char*,int,double的参数顺序!!如果不是这样的顺序,程序将不定期崩溃!!这就

是为什么c不是类型安全的,boost::Format好像提供类型安全的格式转换,可以替代sprintf的使用。
对于sprintf()而言,va_arg(av,v)v的值,使用 char* fmt 中提取的。
 
附:

3)//示例代码2:扩展——自己实现简单的可变参数的函数。(没有用到va_list等宏)
下面是一个简单的printf函数的实现,参考了<The C Programming Language>中的例子
#include "stdio.h"
#include "stdlib.h"
void myprintf(char* fmt, ...)        //一个简单的类似于printf的实现,//参数必须都是int 类型

    char* pArg=NULL;               //等价于原来的va_list 
    char c;
    
    pArg = (char*) &fmt;          //注意不要写成p = fmt !!因为这里要对//参数取址,而不是取值
    pArg += sizeof(fmt);         //等价于原来的va_start          
 
    do
    {
        c =*fmt;
        if (c != '%')
        {
            putchar(c);            //照原样输出字符
        }
        else
        {
           //按格式字符输出数据
           switch(*++fmt) 
           {
            case'd':
                printf("%d",*((int*)pArg));           
                break;
            case'x':
                printf("%#x",*((int*)pArg));
                break;
            default:
                break;
            } 
            pArg += sizeof(int);               //等价于原来的va_arg
        }
        ++fmt;
    }while (*fmt != '/0'); 
    pArg = NULL;                               //等价于va_end
    return; 
}
int main(int argc, char* argv[])
{
    int i = 1234;
    int j = 5678;
    
    myprintf("the first test:i=%d/n",i,j); 
    myprintf("the secend test:i=%d; %x;j=%d;/n",i,0xabcd,j); 
    system("pause");
    return 0;
}

目录
相关文章
|
2月前
|
存储 前端开发 JavaScript
前端基础(十一)_函数声明及调用、函数的形参与实参、arguments参数、函数的参数类型、函数中的问题
本文介绍了JavaScript中函数的声明及调用、形参与实参的概念、arguments对象的使用、函数参数的类型以及函数中this的作用。通过示例代码详细解释了函数如何接收参数、如何处理参数个数不匹配的情况,以及函数在不同上下文中this的指向。
22 1
|
6月前
|
算法 搜索推荐 C语言
C可变参数探究与应用
C可变参数探究与应用
39 2
|
6月前
|
存储
07-python函数的进阶-函数的多返回值/函数的多种传参方式/匿名函数/lambda函数
07-python函数的进阶-函数的多返回值/函数的多种传参方式/匿名函数/lambda函数
|
6月前
|
供应链 算法 安全
掌握Go语言:函数精髓,定义、参数、多返回值与应用(14)
掌握Go语言:函数精髓,定义、参数、多返回值与应用(14)
|
测试技术 Python
软件测试|Python函数参数之必传参数、默认参数、可变参数、关键字参数的详细使用
软件测试|Python函数参数之必传参数、默认参数、可变参数、关键字参数的详细使用
90 0
19.从入门到精通:Python函数 定义一个函数 函数调用 参数传递
19.从入门到精通:Python函数 定义一个函数 函数调用 参数传递
python函数不能传可变参数
python函数不能传可变参数
|
存储 JavaScript 前端开发
函数声明和函数表达式有什么区别和联系,应该怎么用,什么时候用?
函数声明和函数表达式有什么区别和联系,应该怎么用,什么时候用?
126 0
函数声明和函数表达式有什么区别和联系,应该怎么用,什么时候用?
可积与原函数存在的区别
可积与原函数存在的区别
235 0