一文让你搞懂 C语言可变参数 VA_LIST原理详解
文章目录
- 前言
- VA_LIST 简介
- VA_LIST的用法:
- VA_LIST的实现
- sylar学习项目中遇到的例子
- 拓展——变长参数宏
- 注意
前言
在学习C++高性能框架Sylar时遇到的新知识,特以此记录,另外对于C/C++宏的基本使用不太清晰的小伙伴可以看我的这篇博客 C/C++宏的基本使用方法附例子讲解
VA_LIST 简介
VA_LIST
是在C语言中解决变参问题的一组宏,变参问题是指参数的个数不定,可以是传入一个参数也可以是多个;可变参数中的每个参数的类型可以不同,也可以相同;可变参数的每个参数并没有实际的名称与之相对应,用起来是很灵活。
VA_LIST的用法:
首先在函数里定义一具
VA_LIST
型的变量 ,这个变量是指向参数的指针 ,通过指针运算来调整访问的对象;然后用
VA_START
宏初始化变量刚定义的VA_LIST
变量 ,实际上 就是用VA_LIST
去指向函数的最后一个具名的参数;然后用
VA_ARG
宏返回可变的参数,VA_ARG
的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG
获取各个参数);因为栈地址是从高到低延伸的,所以加上你要的参数类型大小,就意味着栈顶指针指向你所要的参数,便可通过 底层 pop 得到。
最后用
VA_END
宏结束可变参数的获取,即清空va_list
。
VA_LIST的实现
上面是 va_list
的具体用法,下面讲解一下 va_list
各个语句含义(如上示例黑体部分)和 va_list
的实现。
变长参数的实现得益于C语言默认的cdecl调用惯例的自右向左压栈传递方式
这里首先要明白函数的参数是存放在栈中,地址是连续的,所以可以通过相对位置去访问,这也是可变参数的访问方式。
可变参数是由宏实现的,但是由于硬件平台的不同,编译器的不同,宏的定义也不相同,下面是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格式化输出到字符串strint len = vasprintf(&buf, fmt, al);if(len != -1) {m_ss << std::string(buf, len);free(buf);}
}
拓展——变长参数宏
在很多时候我们希望在定义宏的时候也能够像print一样可以使用变长参数,即宏的参数可以是任意个,这个功能可以由编译器的变长参数宏实现。在GCC编译器下,变长参数宏可以使用“##”宏字符串连接操作实现,比如:
#define printf(args...) fprintf(stdout, ##args)
那么printf(“%d%s”,123,“hello”)就会被展开成:
fprintf(stdout,"td &s", 123, "hello");
而在MSVC下,我们可以使用_VA_ARGS_这个编译器内置宏,比如:
#define printf(...) fprintf(stdout,__VA_ARGS_)
注意
使用VA_LIST应该注意的问题:
- 因为
va_start
,va_arg
,va_end
等定义成宏,只是字符替换,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. - 另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.不利于我们写出高质量的代码。
- 由于参数的地址用于
VA_START
宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。 - C++11新特性可变参数可以在语言层面很好的解决上述问题
参考文章:
https://blog.csdn.net/ID314846818/article/details/51074283
一文让你搞懂 C语言可变参数 VA_LIST原理详解相关推荐
- 一文带你搞懂Go语言函数选项模式,Go函数一等公民。
前言 通过这篇文章<为什么说Go的函数是"一等公民">,我们了解到了什么是"一等公民",以及都具备哪些特性,同时对函数的基本使用也更加深入. 本文重 ...
- Go语言核心知识点和原理详解
go核心原理 本人在一家go技术栈工作2年有余,因此梳理一下我认为比较重要的go语言技术知识,一些基础的概念,比如function, interface这些就忽略了. https://dravenes ...
- c语言的程序运行原理图,C语言main函数的原理详解
C语言标准在一开始(C90标准 5.1.2条),就规定了程序的执行环境.对于没有操作系统的环境来说,C程序的入口函数是什么都可以(也就是说的在单片机的C程序里,或者在操作系统的底层代码的C入口处,不需 ...
- RPC框架:一文带你搞懂RPC
RPC是什么(GPT答) ChatGPT回答: RPC(Remote Procedure Call)是一种分布式应用程序的编程模型,允许程序在不同的计算机上运行.它以一种透明的方式,将一个程序的函数调 ...
- RPC框架:从原理到选型,一文带你搞懂RPC
大家好,我是华仔,RPC系列的文章是我去年写的,当时写的比较散,现在重新进行整理.对于想学习RPC框架的同学,通过这篇文章,让你知其然并知其所以然,便于以后技术选型,下面是文章内容目录: RPC 什么 ...
- 一文带你搞懂从动态代理实现到Spring AOP
摘要:本文主要讲了Spring Aop动态代理实现的两种方式. 1. Spring AOP Spring是一个轻型容器,Spring整个系列的最最核心的概念当属IoC.AOP.可见AOP是Spring ...
- C语言指针超全面透析(原来你一直没有搞懂C语言指针是因为没有理解其中的规律)
文章目录 写在前面 一.思考指针的基础 1.指针的实质 2.指针的层次 3.指针的分类 4.两个符号(&和*) 二.单指针(int *p) 三.指针数组(int *p[10]) 四.行指针(i ...
- 一文带你搞懂C#多线程的5种写法
一文带你搞懂C#多线程的5种写法 1.简介 超长警告! 在学习本篇文章前你需要学习的相关知识: 线程基本知识 此篇文章简单总结了C#中主要的多线程实现方法,包括: Thread 线程 ThreadPo ...
- 一文搞懂 Cocos Creator 3.0 坐标转换原理
一文搞懂 Cocos Creator 3.0 坐标转换原理 屏幕坐标 UI 触点坐标 UI 多分辨率适配方案 UI 触点获取 不同坐标之间的转换 屏幕坐标与 3D 节点世界坐标互转 3D 节点之间的坐 ...
- 面试官问你斐波那契数列的时候不要高兴得太早 搞懂C语言函数指针 搜索引擎还可以这么玩? 那些相见恨晚的搜索技巧...
面试官问你斐波那契数列的时候不要高兴得太早 前言 假如面试官让你编写求斐波那契数列的代码时,是不是心中暗喜?不就是递归么,早就会了.如果真这么想,那就危险了. 递归求斐波那契数列 递归,在数学与计算机 ...
最新文章
- JVM学习--(一)基本原理
- 软件架构:5种你应该知道的模式
- 全球及中国智能音箱市场规模产值及发展机遇研究报告2021-2027年
- OpenGL Blinn-Phong Shader实例
- PPT如何让多对象排列整齐
- TRUNCATE恢复-bbed
- 对于初学者,如何轻松学习JavaScript?
- Qt Creator 自动补齐变慢的解决
- Ubuntu16.04下 编译安装 Tensorflow
- 为何吾博客周排名没有数字显示?
- 巴比特 | 元宇宙每日必读:红杉中国“雇”了一位虚拟员工,自称每秒可看百份商业计划书,期待时薪为0.68元...
- 复旦计算机考研规划,2021年复旦大学软件工程专业考研经验全指导
- 软件测试报告有哪些内容?
- 解决Wireshark抓包跟踪流后http的响应正文乱码
- 桌面放大镜、演示工具推荐——ZoomIt
- Pure Virtual Function
- 考研政治---马克思主义基本原理概论---唯物史观
- 像素游戏画法_点画法遇到像素化
- uniapp 选择元素,操作元素属性
- TCAM与HASH表的差异