Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【c语法】##__VA_ARGS__与__VA_ARGS__

【c语法】##__VA_ARGS__与__VA_ARGS__

作者头像
破晓的历程
发布于 2024-08-29 00:02:46
发布于 2024-08-29 00:02:46
1.3K02
代码可运行
举报
文章被收录于专栏:破晓破晓
运行总次数:2
代码可运行

引言

在调试过程中,我们经常会自定义打印,比如日志信息的输出,这时就会用VA_ARGS,接下来详细讲解!

VA_ARGS

__VA_ARGS__是C语言设定的一个预定义宏,用于处理可变参数的参数列表。通常用在宏定义中,以便宏可以接收不定数量的参数,并将他们作为整体处理。 通常的函数参数列表都是固定的,但也存在着不定参数数量的函数,如:printf ,为了定义可以处理不同参数个数的宏,C99标准引入了 VA_ARGS,下列的代码均在C99及C99以上标准的环境下运行。

__VA_ARGS__表示所有在宏调用中传递的额外参数。它可以放在宏定义的参数列表的末尾,用于接收任意数量的额外参数,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define LOG_DEBUG(format,...) printf(format,__VA_ARGS__)

解析:

  • DEBUG_LOG 是一个宏,接受至少一个参数 fmt(格式化字符串),后面可以是任意数量的参数。
  • VA_ARGS 表示可变参数列表,在宏展开时会被替换为传递给 DEBUG_LOG 宏的所有实际参数。 示例:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define LOG_(format,...) printf("[%s:%d]"format"\n",__FILE__,__LINE__,__VA_ARGS__);

如上的代码就是对日志定义的一个宏。


##VA_ARGS

大家在了解__VA_ARGS__时,一定会看到有些地方在该宏定义前使用 ## 运算符来处理可变参数,如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define DEBUG_LOG(fmt, ...) \
    printf("[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)

##__VA_ARGS__前面加上##的作用是:当可变参数的个数为0时,这里的##可以把把前面多余的","去掉,否则会编译出错。 当想要在自定义的调试信息加上时间、行数等信息时,应该怎么做呢?先把正确的用法写在前面:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>

#define LOG3(fmt, ...)          printf("<%s:%s>:"fmt"\r\n", __FILE__, __FUNCTION__, ##__VA_ARGS__)
int main(int argc, char** argv)
{
    char *str = "test __VA_ARGS__";
    int num = 10086;
    
    LOG3("this is test __VA_ARGS__");
    LOG3("this is test __VA_ARGS__:%s, %d", str, num);
    LOG3();

    return 0;
}

结果为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<main.c:main>:this is test __VA_ARGS__
<main.c:main>:this is test __VA_ARGS__:test __VA_ARGS__, 10086
<main.c:main>:

但是如果不加##:可变参数为空时,报错。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>

#define LOG2(fmt, ...)          printf("<%s:%s>:"fmt"\r\n", __FILE__, __FUNCTION__, __VA_ARGS__)
int main(int argc, char** argv)
{
    LOG2();//不传参数,编译报错
    LOG2("this is test __VA_ARGS__");//编译报错
    LOG2("this is test __VA_ARGS__:%s, %d", str, num);//正常编译
    return 0;
}

分析:

  • 第6行没有传参数,宏定义LOG2(fmt,…)展开后,__VA_ARGS__是空的,这时printf后面剩余一个,,必然编译失败;
  • 第7行虽然传入了字符串,但是该字符串赋值给了LOG2(fmt,…)的第一个参数fmt,宏展开后,printf后面也剩余一个,,所以编译报错。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
变参函数和可变参数宏
前言:此文关于宏的内容摘抄自裸机思维公众号的"为宏证明"系列文章,本文对原文内容做了大量精简,所以难免会有晦涩难懂的地方,想要更深入了解宏的用法,还请移步到裸机思维。
AIoT-KK
2023/02/09
2.3K0
变参函数和可变参数宏
整理C/C++的可变参数
c语言中使用可变参数最熟悉应该就是printf, 其是通过...来从代码语句中表示可变化的参数表。
Rock_Lee
2020/09/21
5.8K0
[C语言] #、##、__VA_ARGS__的使用
从网上借鉴了一些内容,然后整理了一下三者的区别,记录一下。 #include <iostream> // #: 用来把参数转换成字符串 #define LOG1(x) do { printf("%s=%d\n",#x,x); }while(0) // ##:用于将带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串;但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元; #define LOG2(x) log##x() // __VA_ARGS_
轻舞飞扬SR
2021/03/15
1.2K0
提高代码逼格的利器:宏定义-从入门到放弃
一直以来,我都有这样一种感觉:当我学习一个新领域的知识时,如果其中的某个知识点在刚开始接触时,我感觉比较难懂、不好理解,那么以后不论我花多长时间去研究这个知识点,心里会一直认为该知识点比较难,也就是说第一印象特别的重要。
IOT物联网小镇
2021/05/13
1.3K0
提高代码逼格的利器:宏定义-从入门到放弃
【在线五子棋对战】六、项目结构设计 && 工具模块实现
​ 为什么说分为这三大块呢,下面画个图来大概描述一下整个客户端和服务器的通信过程:
利刃大大
2025/06/15
1320
【在线五子棋对战】六、项目结构设计 && 工具模块实现
C语言边角料-01
这几天在把一个嵌入式项目的代码,移植到另一个平台,发现很多地方用的都是 C89 标准。
IOT物联网小镇
2021/05/13
5230
C语言边角料-01
【C99】#,##,#__VA_ARGS__,##__VA_ARGS__
#运算符用于将宏参数转换为字符串字面量。这通常用于调试或日志记录,将参数的值以字符串形式输出。
用户11396661
2025/03/30
1120
【C99】#,##,#__VA_ARGS__,##__VA_ARGS__
C中的预编译宏定义
文章来自 http://www.uml.org.cn/c++/200902104.asp 在将一个C源程序转换为可执行程序的过程中, 编译预处理是最初的步骤. 这一步骤是由预处理器(preprocessor)来完成的. 在源流程序被编译器处理之前, 预处理器首先对源程序中的"宏(macro)"进行处理. C初学者可能对预处理器没什么概念, 这是情有可原的: 一般的C编译器都将预处理, 汇编, 编译, 连接过程集成到一起了. 编译预处理往往在后台运行. 在
杨奉武
2018/04/18
3.2K0
《不定参数与不定宏参数:C++程序员必须掌握的灵活编程技巧》
在 C++ 中,不定参数函数(Variadic Functions) 是一种可以接受数量不确定的参数的函数。这种机制常用于像 printf 这样的标准库函数,也广泛应用于日志系统、格式化输出、通用容器构造等场景
IsLand1314
2025/05/01
1700
编程这么久, 它们了解多少(一)
对于日志,一般情况下可以设置日志输出等级、输出到终端或文件、输出到每个文件的大小、日志被覆盖的策略,还有的可以在程序运行过程中更改日志的等级,或者将日志输出到远程服务器(至今没有接触到)等。
用户5908113
2020/11/09
5010
编程这么久, 它们了解多少(一)
同步&异步日志系统:前置知识
1、⽣产环境的产品为了保证其稳定性及安全性是不允许开发⼈员附加调试器去排查问题,可以借助日志系统来打印⼀些⽇志帮助开发⼈员解决问题
小陈在拼命
2024/12/21
2450
同步&异步日志系统:前置知识
C/C++开发基础——可变参数与可变参数模板
1.如果可变参数的参数类型相同,可以使用标准库中的initializer_list。
Coder-ZZ
2023/09/04
9910
C/C++开发基础——可变参数与可变参数模板
日志代码编写
  日志是程序周期性运转或者特定时刻等一些常规或者特殊消息以特殊的形式打印出来,我们称为日志,关于日志,AI是这样回答的:
用户11029129
2024/11/02
2400
日志代码编写
长文详解:C语言预处理命令
预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作。预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置。
C语言与CPP编程
2020/12/02
3.2K0
编程这么久, printf 知多少(二)
gcc 编译器会遇到的error :does not give a valid preprocessing token
用户5908113
2020/11/09
8310
编程这么久, printf 知多少(二)
C++雾中风景番外篇4:GCC升级二三事
这个我感觉是历史的遗留问题了,从C++11开始就不支持字符串字面量后面直接连接变量名,GCC 4.8.2应该是没有支持该编译检查,所以后续升级8.2的时候报了类似的错误。
HappenLee
2020/02/24
8540
C语言打印程序行号、日期方便调试程序
平时开发C语言程序时,经常需要调试代码,C语言有一些宏,可以打印出当前的行号、文件名称、日期、时间,对程序的调试起到很大的帮助,可以快速定位问题。特别是开发单片机程序时,使用这些宏打印这些信息或者在LCD上显示程序的编译日期、时间,可以知道这个单片机上的固件是什么时候编译。帮助判断版本。
DS小龙哥
2022/06/17
2.3K0
C语言打印程序行号、日期方便调试程序
iOS中的预编译指令的初步探究
开篇 我们人类创造东西的时候有个词叫做”仿生学“!人类创造什么东西都会模仿自己来创造,所以上帝没有长成树的样子而和人长得一样,科幻片里面外星人也像人一样有眼睛有鼻子……但是人类自己创造的东西如果太像自己,自己又会吓尿(恐怖谷效应),人类真是奇葩;奇葩的我们在20世纪创造了改变世界的东西——计算机(电脑),不用怀疑,这货当然也是仿生学!这货哪里长得像人了??别不服,先听我说完,先把你的砖头放下。狭义的仿生学是外形上仿生嘛,其实广义上仿生学还可以原理的仿生,构造的仿生,性能的仿生阿拉巴拉……,计算机(这里我狭义
王大锤
2018/05/17
2.4K0
C/C++总结
freopen("CONOUT$", "w", stdout);//重定向输出到控制台
用户7886150
2021/02/20
9050
初识Linux · 日志编写
在线程池部分我们纵观全文,可以发现全文有很多很多的IO流,看起来还是差点意思的,而我们今天提到的日志,是在今后的代码编写中会经常接触,或者说在这之前,我们也接触过日志,不过我们没有注意而已,比如平常常用的电脑,强制关机什么的都会在日志里面。
_lazy
2024/12/20
2450
初识Linux · 日志编写
相关推荐
变参函数和可变参数宏
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验