一文让你搞懂 C语言可变参数 VA

一文让你搞懂 C语言可变参数 VA,第1张

文章目录
    • 前言
    • VA_LIST 简介
      • VA_LIST的用法:
      • VA_LIST的实现
      • sylar学习项目中遇到的例子
      • 注意

前言

在学习C++高性能框架Sylar时遇到的新知识,特以此记录,另外对于C/C++宏的基本使用不太清晰的小伙伴可以看我的这篇博客 C/C++宏的基本使用方法附例子讲解

VA_LIST 简介

VA_LIST 是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。

VA_LIST的用法:
  1. 首先在函数里定义一具 VA_LIST 型的变量 ,这个变量是指向参数的指针 ,通过指针运算来调整访问的对象;

  2. 然后用 VA_START 宏初始化变量刚定义的 VA_LIST 变量 ,实际上 就是用 VA_LIST 去指向可变参数的第一个元素;

  3. 然后用 VA_ARG 宏返回可变的参数,VA_ARG 的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用 VA_ARG 获取各个参数);

    因为栈地址是从高到低延伸的,所以加上你要的参数类型大小,就意味着栈顶指针指向你所要的参数,便可通过 底层 pop 得到。

  4. 最后用 VA_END 宏结束可变参数的获取,即清空 va_list

VA_LIST的实现

上面是 va_list 的具体用法,下面讲解一下 va_list 各个语句含义(如上示例黑体部分)和 va_list 的实现。

这里首先要明白函数的参数是存放在栈中,地址是连续的,所以可以通过相对位置去访问,这也是可变参数的访问方式。

可变参数是由宏实现的,但是由于硬件平台的不同,编译器的不同,宏的定义也不相同,下面是VC6.0中x86平台的定义 :

  typedef char * va_list;     // TC中定义为void*
  #define _INTSIZEOF(n)    ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) //为了满足需要内存对齐的系统
  #define va_start(ap,v)    ( ap = (va_list)&v + _INTSIZEOF(v) )     //ap指向第一个变参的位置,即将第一个变参的地址赋予ap
  #define va_arg(ap,t)       ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )   /*获取变参的具体内容,t为变参的类型,如有多个参数,则通过移动ap的指针来获得变参的地址,从而获得内容*/
  #define va_end(ap) ( ap = (va_list)0 )   //清空va_list,即结束变参的获取
  • va_list ap ; 定义一个 va_list 变量 ap
  • va_start(ap,v) ;执行 ap = (va_list)&v + _INTSIZEOF(v)ap 指向参数 v 之后的那个参数的地址,即 ap指向第一个可变参数在堆栈的地址。
  • va_arg(ap,t)( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 取出当前ap指针所指的值,并使 ap 指向下一个参数。 ap+=sizeof(t类型) ,让ap指向下一个参数的地址。然后返回 ap - sizeof(t类型) 的t类型指针,这正是第一个可变参数在堆栈里的地址。然后 用取得这个地址的内容。
  • va_end(ap) ; 清空 va_list ap
sylar学习项目中遇到的例子

sylar/log.cc

void LogEvent::format(const char* fmt, ...) {
    //定义一个 `va_list` 变量 `al`
    va_list al;
    //使 al 指向 fmt 后面那个参数,栈中参数地址连续,所以可以做到
    va_start(al, fmt);
    //函数重载,调用下面那个
    format(fmt, al);
    va_end(al);
}

void LogEvent::format(const char* fmt, va_list al) {
    char* buf = nullptr;
    //C 库函数 - int vsprintf(char *str, const char *format, va_list arg) 使用参数列表arg按format格式化输出到字符串str
    int len = vasprintf(&buf, fmt, al);
    if(len != -1) {
        m_ss << std::string(buf, len);
        free(buf);
    }
}
注意

使用VA_LIST应该注意的问题:

  1. 因为 va_start , va_arg , va_end 等定义成宏,只是字符替换,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
  2. 另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。
  3. 由于参数的地址用于 VA_START 宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。
  4. C++11新特性可变参数可以在语言层面很好的解决上述问题

参考文章:
https://blog.csdn.net/ID314846818/article/details/51074283

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1329899.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存