首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >GCC的##__VA_ARGS__戏法的标准替代品?

GCC的##__VA_ARGS__戏法的标准替代品?
EN

Stack Overflow用户
提问于 2011-04-07 23:39:28
回答 11查看 93.4K关注 0票数 164

有一个人所共知 问题,为C99中的各种宏提供空的args。

示例:

代码语言:javascript
复制
#define FOO(...)       printf(__VA_ARGS__)
#define BAR(fmt, ...)  printf(fmt, __VA_ARGS__)

FOO("this works fine");
BAR("this breaks!");

根据BAR()标准,上述C99的使用确实是不正确的,因为它将扩展到:

代码语言:javascript
复制
printf("this breaks!",);

注意后面的逗号--不可行。

一些编译器(例如: Visual 2010)会悄悄地为您去掉这个后缀逗号。其他编译器(例如: GCC)支持将##放在__VA_ARGS__前面,如下所示:

代码语言:javascript
复制
#define BAR(fmt, ...)  printf(fmt, ##__VA_ARGS__)

但是是否有一种符合标准的方法来获得这种行为呢?也许使用多个宏?

现在,##版本看起来相当受支持(至少在我的平台上是这样),但我更愿意使用符合标准的解决方案。

先发制人:我知道我可以写一个小函数。我试着用宏来做这件事。

编辑:下面是一个示例(虽然很简单),说明我为什么要使用BAR():

代码语言:javascript
复制
#define BAR(fmt, ...)  printf(fmt "\n", ##__VA_ARGS__)

BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);

这会自动为BAR()日志语句添加一个换行符,前提是fmt总是一个双引号的C-字符串。它不将换行符打印为单独的printf(),如果日志记录是行缓冲的,并且异步地来自多个源,这是有利的。

EN

回答 11

Stack Overflow用户

回答已采纳

发布于 2011-04-08 01:08:09

可以避免使用GCC的,##__VA_ARGS__扩展,如果您愿意接受一些硬编码的上限,您可以传递给您的变量宏的数量,如理查德·汉森对这个问题的回答所描述的。但是,据我所知,如果您不希望有任何这样的限制,就不可能只使用C99指定的预处理器特性;您必须使用该语言的一些扩展。clang和icc已经采用了GCC的这一扩展,但MSVC没有。

早在2001年,我就在文档N976中写下了GCC的标准化扩展(以及相关的扩展,允许您使用__VA_ARGS__以外的名称作为rest参数),但这没有得到委员会的任何响应;我甚至不知道是否有人读过它。2016年,N2023再次提出了这一建议,我鼓励任何知道该提案将如何在评论中告知我们的人。

票数 70
EN

Stack Overflow用户

发布于 2012-06-23 20:19:19

有一个你可以使用的论点计数技巧。

以下是实现jwd问题中的第二个BAR()示例的一种标准兼容的方法:

代码语言:javascript
复制
#include <stdio.h>

#define BAR(...) printf(FIRST(__VA_ARGS__) "\n" REST(__VA_ARGS__))

/* expands to the first argument */
#define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway)
#define FIRST_HELPER(first, ...) first

/*
 * if there's only one argument, expands to nothing.  if there is more
 * than one argument, expands to a comma followed by everything but
 * the first argument.  only supports up to 9 arguments but can be
 * trivially expanded.
 */
#define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__)
#define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__)
#define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__)
#define REST_HELPER_ONE(first)
#define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__
#define NUM(...) \
    SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,\
                TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway)
#define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10

int
main(int argc, char *argv[])
{
    BAR("first test");
    BAR("second test: %s", "a string");
    return 0;
}

同样的技巧也适用于:

解释

策略是将__VA_ARGS__分为第一个参数和其余参数(如果有的话)。这样就可以在第一个参数之后插入内容,而在第二个参数之前插入内容(如果存在的话)。

FIRST()

这个宏只是扩展到第一个参数,放弃了其余的参数。

实现是简单明了的。throwaway参数确保FIRST_HELPER()获得两个参数,这是必需的,因为...至少需要一个参数。有一个论点,它扩展如下:

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

在两个或两个以上的情况下,它扩展如下:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. firstarg

REST()

这个宏扩展到除第一个参数之外的所有内容(如果有多个参数,则包括第一个参数之后的逗号)。

这个宏的实现要复杂得多。一般策略是计算参数的数量(一个或多个),然后扩展到REST_HELPER_ONE() (如果只给出一个参数)或REST_HELPER_TWOORMORE() (如果给定两个或多个参数)。REST_HELPER_ONE()只是扩展到零--在第一个参数之后没有参数,所以剩下的参数是空集合。REST_HELPER_TWOORMORE()也很简单--它扩展为逗号,后面跟着除第一个参数之外的所有内容。

参数使用NUM()宏进行计数。如果只给出一个参数,则这个宏扩展到ONE;如果给出两个到九个参数,则扩展为TWOORMORE;如果给出10个或多个参数,则中断(因为它扩展到第10个参数)。

NUM()宏使用SELECT_10TH()宏来确定参数的数量。顾名思义,SELECT_10TH()只是扩展到它的第10个参数。由于省略号,SELECT_10TH()至少需要传递11个参数(标准规定必须至少有一个参数用于省略)。这就是为什么NUM()throwaway作为最后一个参数传递(如果没有它,将一个参数传递给NUM()只会导致10个参数被传递给SELECT_10TH(),这将违反标准)。

REST_HELPER_ONE()REST_HELPER_TWOORMORE()的选择是通过将REST_HELPER_NUM(__VA_ARGS__)REST_HELPER2()中的扩展连接起来完成的。请注意,REST_HELPER()的目的是确保在与REST_HELPER_连接之前完全展开NUM(__VA_ARGS__)

有一个论点的扩展如下:

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (空)

有两个或两个以上参数的扩展如下:

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg
票数 121
EN

Stack Overflow用户

发布于 2011-12-29 21:48:19

不是一般的解决方案,但在printf的情况下,您可以追加如下新行:

代码语言:javascript
复制
#define BAR_HELPER(fmt, ...) printf(fmt "\n%s", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, "")

我认为它忽略了没有在格式字符串中引用的任何额外的arg。所以你甚至可以逃脱:

代码语言:javascript
复制
#define BAR_HELPER(fmt, ...) printf(fmt "\n", __VA_ARGS__)
#define BAR(...) BAR_HELPER(__VA_ARGS__, 0)

我不敢相信C99在没有标准方法的情况下被批准了。C++11也存在这一问题。

票数 18
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5588855

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档