zoukankan      html  css  js  c++  java
  • 带变长参数的函数

    带变长参数的函数

    很多语言都支持带变长参数的函数,C也不例外,我们常用的比如printf()函数,它的函数原型中参数列表里面有一个省略号,就代表了可变参数列表,可以先看下它的实现:

    int printf(char *fmt, ...)
    {
        static char sprint_buf[1024];
    
        va_list args;              // 初始化指向可变参数列表的指针args
        int n;
        va_start(args, fmt);        //args指向可变参数表的起始位置
        n = vsprintf(sprint_buf, fmt, args);
        va_end(args);             // 将args复位
        write(1, sprint_buf, n); 
        return n;
    }

    可能你现在还不明白这段代码,但大概功能能看出来吧,先是把可变参数传给vsprintf,由它负责将内容输出到buf中,然后将buf打印到屏幕上。

    但有人可能会对以va_开头的那几个函数可能会比较陌生,下面我列出了它们的函数原型:

    #include <stdarg.h>
    void va_start(va_list ap, argN);
    type va_arg(va_list ap, type);
    void va_copy(va_list dest, va_list src);
    void va_end(va_list ap);

    实际上,这几个函数都不是函数,而是宏定义:

    typedef char *va_list;      
    
    #define  _AUPBND        (sizeof (acpi_native_int) - 1)
    #define  _ADNBND        (sizeof (acpi_native_int) - 1)
    #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))))

    从上面的宏看好像它们都是在计算一些偏移位置而已。OK,先回过头来看看函数调用的过程,然后就会明白这些偏移值的计算原理。

    我们知道计算机中函数调用有两种方式,分别是stdcall与cdecl,它们的区别在于:

    1、_stdcall 是Pascal程序的缺省调用方式,通常用于 Win32 API中。按从右至左的顺序压参数入栈。 在主调函数中负责压栈,在被调函数中返回前负责清理栈。 由于被调函数并不知道传进来的参数个数,因此 _stdcall不适合可变长度参数的函数。
    2、_cdecl (The C default calling convention)是C/C++ 调用约定,也是按从右至左的顺序压参数入栈,并且由调用者把弹出栈,因此,实现可变参数的函数只能使用该调用约定。

    显然,我们的可变参数使用了_cdecl协议调用函数,发生函数调用时,主调函数将参数按照从右往左的顺序压入堆栈,被调函数返回后,主调函数还要对栈进行清理。

    看一个简单的例子:

    #include <stdarg.h>
    
    void fun(int n, ...)
    {
        int i, temp;
        va_list arg;
        va_start(arg, n); 
    
    for (i = 0; i < n; ++i) { temp = va_arg(arg, int);  // 取出参数 printf("%d ", temp); } va_end(arg); } int main() { int a = 1; int b = 2; int c = 3; int d = 4; fun(4, a, b, c, d); return 0; }

    首先,声明了一个 va_list(就是char*指针)的参数列表args,

    va_list args;

    然后用va_start 宏来获取参数列表中的参数,这里args参数是刚声明的一个char*指针,自不必多说,n是位于栈顶的固定参数。

     va_start(args, n);  

    我们知道栈是由高地址向低地址生长,va_start使arg指向了第一个可选参数,

    va_arg(args, int);  

    每次调用va_arg之后(需要指定下一个参数的类型),args就指向下一个参数,这就是前面那一坨宏定义干的事情。

    现在我们再来想想printf()函数,它的第一个参数是fmt,根据'%'就能确定后面的可变参数个数和每个参数的类型,这样就能通过va_arg依次从栈中定位到每个参数的位置。

    (夜深了,还是先睡吧,未完待续。。。)

  • 相关阅读:
    【计算机视觉】深度相机(三)--三种方案对比
    【计算机视觉】深度相机(三)--三种方案对比
    【计算机视觉】深度相机(二)--结构光深度测距
    【计算机视觉】深度相机(二)--结构光深度测距
    【计算机视觉】深度相机(一)--TOF总结
    【计算机视觉】深度相机(一)--TOF总结
    【计算机视觉】人脸表情识别技术
    Http报头Accept与Content-Type的区别
    ajax上传文件,并检查文件类型、检查文件大小
    oracle 生成随机数【待整理】
  • 原文地址:https://www.cnblogs.com/chenny7/p/3684242.html
Copyright © 2011-2022 走看看