C语言变参宏

巧了我就是萌 提交于 2020-02-26 10:39:12

背景:

C语言,GCC4.6,测试环境VS6.0

模拟printf的入参形式,达到如下目的:

某系统日志函数入参过多,在同一模块有很多重复的参数,希望能够减少输入,按照const char *format, ...的形式输入需要显示的内容。

研究结果:

在GCC编译环境下

使用GCC的变参宏(Varadic Macros)扩展实现:

 1 /*
 2  * main.c
 3  *
 4  *  Created on: 2015年1月29日
 5  *      Author: lucifet
 6  */
 7 
 8 #include <stdio.h>
 9 #include <stdarg.h>
10 
11 #define APP_DETAIL 0
12 #define APP_MSG    1
13 #define APP_WARN   2
14 #define APP_ERROR  3
15 #define APP_FAIL   4
16 #define APP_FATAL  5
17 
18 #define TRUE 1
19 
20 #define LogTraceAPP_DETAIL(id,LogId,Module,PrnLevel, Format,args...) do{\
21     printf("id = %ld, LogId = %ld, Module = %ld, PrnLevel = %uc\n", \
22             id,LogId,Module,PrnLevel); \
23     printf(Format, ##args); \
24 }while(0)
25 
26 #define Log(id,LogId,Module,PrnLevel, Format,args...)  \
27         LogTrace##PrnLevel(id,LogId,Module,PrnLevel, Format,##args)
28 
29 #define LOGMSG_DETAIL(format, ...) \
30 { \
31     Log(1024, __LINE__, \
32             __LINE__, APP_DETAIL, "DETAIL:[Inst %u] "format, (4096u), ##__VA_ARGS__);\
33 }
34 
35 int main(void)
36 {
37     LOGMSG_DETAIL("$$$$$$$$$\n");
38     LOGMSG_DETAIL("%s = %d $$$$$$$\n", "f", 'f');
39 
40     return 0;
41 }

输出:

id = 1024, LogId = 37, Module = 37, PrnLevel = 0c
DETAIL:[Inst 4096] $$$$$$$$$
id = 1024, LogId = 38, Module = 38, PrnLevel = 0c
DETAIL:[Inst 4096] f = 102 $$$$$$$

 

在Visual Studio 6.0环境下

使用全局变量存储非varadic parameters的信息,利用可变参数函数模拟可变参数宏的实现:

 1 #include <stdio.h>
 2 #include <stdarg.h>
 3 
 4 int gInteger1;
 5 int gInteger2;
 6 
 7 #define TEST_MACRO gInteger1 = 1; gInteger2 = 2;testFunc
 8 
 9 void testFunc(const char *format, ...)
10 {
11     char cArray[1000] = {0};
12     va_list argList;
13 
14     printf("I'm in testFunc\n");
15     printf("%d, %d\n", gInteger1, gInteger2);
16 
17     va_start(argList, format);
18     vsprintf((char *)cArray, format, argList);
19     va_end(argList);
20     printf("%s", cArray);
21 }
22 
23 int main(void)
24 {
25     TEST_MACRO("%s = %d\n", "a", 'a');
26 
27     gInteger1 = gInteger2 = 3;
28 
29     TEST_MACRO("%d, %d\n", gInteger1, gInteger2);
30     
31     return 0;
32 }

输出:

实现说明:

GCC提供了对变参宏的扩展,大部分与C99对变参宏的支持是相同的(可能是借鉴了GCC的编译扩展)。

 

分为具名的和不具名的两种实现方式:

1、具名实现方式

#define eprintf(args...) fprintf (stderr, args)

这样args就可以作为可变数目参数传入。

2、不具名实现方式

#define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__)

使用__VA_ARGS__来指示varadic parameters

在实际使用中,变参数目可能为0,宏替换后变参前面的“,”由于之后未接参数,会引起编译错误"parse error before ')' token"。

GCC使用"##"来解决这个问题,当"##"加在变参名前面时,在变参数目为0时会将前面的","吞掉:

 https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

If you write

#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
and the variable argument is left out when the eprintf macro is used, then the comma before the ‘##’ will be deleted. This does not happen if you pass an empty argument, nor does it happen if the token preceding ‘##’ is anything other than a comma.

eprintf ("success!\n")
==> fprintf(stderr, "success!\n");

C99和GCC在这里有一点不同:

The above explanation is ambiguous about the case where the only macro parameter is a variable arguments parameter, as it is meaningless to try to distinguish whether no argument at all is an empty argument or a missing argument. In this case the C99 standard is clear that the comma must remain, however the existing GCC extension used to swallow the comma. So CPP retains the comma when conforming to a specific C standard, and drops it otherwise.

当宏唯一的参数是varadic parameters时,对于MACRO();调用区分不了是空参还是忘掉了参数(为什么要区分?)C99明确表示","需要保留,而GCC习惯于把","吞掉。

兼容性问题:

1、两种方式如何选择

Variadic macros are a new feature in C99. GNU CPP has supported them for a long time, but only with a named variable argument (‘args...’, not ‘...’ and __VA_ARGS__). If you are concerned with portability to previous versions of GCC, you should use only named variable arguments. On the other hand, if you are concerned with portability to other conforming implementations of C99, you should use only __VA_ARGS__.

需要兼容较早版本的GCC,采用并只使用具名实现方式;需要兼容C99,采用并只使用__VA_ARGS__。

2、对于上文提到的“多出来的comma”问题

GCC为了与C99保持一致调整了处理方法,为了兼容调整前后的处理方式,需要在“多出来的comma”前后都加上空格

Previous versions of CPP implemented the comma-deletion extension much more generally. We have restricted it in this release to minimize the differences from C99. To get the same effect with both this and previous versions of GCC, the token preceding the special ‘##’ must be a comma, and there must be white space between that comma and whatever comes immediately before it:

    #define eprintf(format, args...) fprintf (stderr, format , ##args)

 

 

在VC6.0下由于不支持Varadic Macro,只能使用变参函数列表模拟。

在宏实现方式的参数中会使用参数"ptInst",这个是固定的实例指针对象名称,采用变参函数实现的话由于进入了不同的scope,不能使用"ptInst"来获取相关信息,因此需要把相关信息存到全局变量中保存,然后调用自定义的变参函数接收变参列表。实际上只是一个文本替换宏。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!