是否可以迭代可变参数宏中的参数?

Posted

技术标签:

【中文标题】是否可以迭代可变参数宏中的参数?【英文标题】:Is it possible to iterate over arguments in variadic macros? 【发布时间】:2010-12-24 17:45:30 【问题描述】:

我想知道是否可以在 C99 中迭代传递给可变参数宏的参数或使用任何 GCC 扩展?

例如是否可以编写一个通用宏,将结构及其字段作为参数传递并打印结构中每个字段的偏移量?

类似这样的:

结构一个 诠释一个; 诠释 b; 诠释 c; ; /* PRN_STRUCT_OFFSETS 将打印每个字段的偏移量 作为第一个参数传递的结构内。 */ int main(int argc, char *argv[]) PRN_STRUCT_OFFSETS(结构 a, a, b, c); 返回0;

【问题讨论】:

【参考方案1】:

这是我今天的作业,它基于宏观技巧,今天我特别了解了__VA_NARG__ invented by Laurent Deniau。无论如何,为了清楚起见,以下示例代码最多可以使用 8 个字段。如果您需要更多,只需通过复制来扩展代码(这是因为预处理器没有递归功能,因为它只读取文件一次)。

#include <stdio.h>
#include <stddef.h>

struct a

  int a;
  int b;
  int c;
;

struct b

  int a;
  int b;
  int c;
  int d;
;

#define STRINGIZE(arg)  STRINGIZE1(arg)
#define STRINGIZE1(arg) STRINGIZE2(arg)
#define STRINGIZE2(arg) #arg

#define CONCATENATE(arg1, arg2)   CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2)  CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2)  arg1##arg2

/* PRN_STRUCT_OFFSETS will print offset of each of the fields 
 within structure passed as the first argument.
 */
#define PRN_STRUCT_OFFSETS_1(structure, field, ...) printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS_2(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_1(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_3(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_2(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_4(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_3(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_5(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
 PRN_STRUCT_OFFSETS_4(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_6(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_5(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_7(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_6(structure, __VA_ARGS__)
#define PRN_STRUCT_OFFSETS_8(structure, field, ...)\
  printf(STRINGIZE(structure)":"STRINGIZE(field)"-%d\n", offsetof(structure, field));\
  PRN_STRUCT_OFFSETS_7(structure, __VA_ARGS__)

#define PRN_STRUCT_OFFSETS_NARG(...) PRN_STRUCT_OFFSETS_NARG_(__VA_ARGS__, PRN_STRUCT_OFFSETS_RSEQ_N())
#define PRN_STRUCT_OFFSETS_NARG_(...) PRN_STRUCT_OFFSETS_ARG_N(__VA_ARGS__) 
#define PRN_STRUCT_OFFSETS_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N 
#define PRN_STRUCT_OFFSETS_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0

#define PRN_STRUCT_OFFSETS_(N, structure, field, ...) CONCATENATE(PRN_STRUCT_OFFSETS_, N)(structure, field, __VA_ARGS__)

#define PRN_STRUCT_OFFSETS(structure, field, ...) PRN_STRUCT_OFFSETS_(PRN_STRUCT_OFFSETS_NARG(field, __VA_ARGS__), structure, field, __VA_ARGS__)

int main(int argc, char *argv[])

  PRN_STRUCT_OFFSETS(struct a, a, b, c);
  printf("\n");
  PRN_STRUCT_OFFSETS(struct b, a, b, c, d);

  return 0;

打印出来的:

struct a:a-0
struct a:b-4
struct a:c-8

struct b:a-0
struct b:b-4
struct b:c-8
struct b:d-12

编辑:这是一个稍微不同的版本,它试图更通用。FOR_EACH(what, ...) 宏将what 应用于变量参数列表中的所有其他参数。

因此,您只需要定义一个接受单个参数的宏,如下所示:

#define DO_STUFF(x) foo(x)

这将应用于列表中的每个参数。 因此,对于您的典型示例,您需要稍微修改一下,但仍然保持简洁:

#define PRN_STRUCT_OFFSETS_(structure, field) printf(STRINGIZE(structure)":"STRINGIZE(field)" - offset = %d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(struct a, field)

然后你像这样应用它:

FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c);

最后,一个完整的示例程序:

#include <stdio.h>
#include <stddef.h>

struct a

  int a;
  int b;
  int c;
;

#define STRINGIZE(arg)  STRINGIZE1(arg)
#define STRINGIZE1(arg) STRINGIZE2(arg)
#define STRINGIZE2(arg) #arg

#define CONCATENATE(arg1, arg2)   CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2)  CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2)  arg1##arg2

#define FOR_EACH_1(what, x, ...) what(x)
#define FOR_EACH_2(what, x, ...)\
  what(x);\
  FOR_EACH_1(what,  __VA_ARGS__);
#define FOR_EACH_3(what, x, ...)\
  what(x);\
  FOR_EACH_2(what, __VA_ARGS__);
#define FOR_EACH_4(what, x, ...)\
  what(x);\
  FOR_EACH_3(what,  __VA_ARGS__);
#define FOR_EACH_5(what, x, ...)\
  what(x);\
 FOR_EACH_4(what,  __VA_ARGS__);
#define FOR_EACH_6(what, x, ...)\
  what(x);\
  FOR_EACH_5(what,  __VA_ARGS__);
#define FOR_EACH_7(what, x, ...)\
  what(x);\
  FOR_EACH_6(what,  __VA_ARGS__);
#define FOR_EACH_8(what, x, ...)\
  what(x);\
  FOR_EACH_7(what,  __VA_ARGS__);

#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
#define FOR_EACH_NARG_(...) FOR_EACH_ARG_N(__VA_ARGS__) 
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N 
#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0

#define FOR_EACH_(N, what, x, ...) CONCATENATE(FOR_EACH_, N)(what, x, __VA_ARGS__)
#define FOR_EACH(what, x, ...) FOR_EACH_(FOR_EACH_NARG(x, __VA_ARGS__), what, x, __VA_ARGS__)

#define PRN_STRUCT_OFFSETS_(structure, field) printf(STRINGIZE(structure)":"STRINGIZE(field)" - offset = %d\n", offsetof(structure, field));
#define PRN_STRUCT_OFFSETS(field) PRN_STRUCT_OFFSETS_(struct a, field)

int main(int argc, char *argv[])

  FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c);
  printf("\n");

  return 0;

【讨论】:

整洁。我想知道是否可以通过将 VA_ARGS 传递给另一个具有命名参数以捕获 VA_ARGS 之一的宏来拆分它,所以我喜欢这个答案.太糟糕了 CPP 让你为每个计数编写宏,而不是允许递归扩展并在没有 args 时做一些不同的事情。我不知道我是否会包含这么大的宏集合,除非它会在某处保存大量代码。好吧,也许是为了我自己在开发过程中使用......无论如何,巧妙的技巧。 这是一个不错的技巧 Gregory。我在谷歌搜索时偶然发现了 VA_NARG 帖子,但不知道(或不知道)您可以使用它来构建基于参数数量的调度程序宏。 GMan,你的方法是我最初采用的方法。 phillipe,X-Macros 是一种有趣的方法。感谢大家的回复。 我已经在***.com/questions/2751870/… 中看到了双字符串化是如何工作的,但是为什么 STRINGIZE 和 CONCATENATE 三个调用很深? Henk > 事实上我不记得为什么了,它在我的代码库中已经存在很长时间了。 1)要么是一些令人毛骨悚然的编译器需要它 2)要么这是我的错误:) 这肯定与旧的代码战士编译器或 msvc++ 6 有关【参考方案2】:

冒着获得考古学家徽章的风险,我认为使用 Overloading Macro on Number of Arguments 中的技术对 Gregory 的上述答案有细微的改进

使用 foo.h:

// Make a FOREACH macro
#define FE_0(WHAT)
#define FE_1(WHAT, X) WHAT(X) 
#define FE_2(WHAT, X, ...) WHAT(X)FE_1(WHAT, __VA_ARGS__)
#define FE_3(WHAT, X, ...) WHAT(X)FE_2(WHAT, __VA_ARGS__)
#define FE_4(WHAT, X, ...) WHAT(X)FE_3(WHAT, __VA_ARGS__)
#define FE_5(WHAT, X, ...) WHAT(X)FE_4(WHAT, __VA_ARGS__)
//... repeat as needed

#define GET_MACRO(_0,_1,_2,_3,_4,_5,NAME,...) NAME 
#define FOR_EACH(action,...) \
  GET_MACRO(_0,__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1,FE_0)(action,__VA_ARGS__)

// Example
// Some actions
#define QUALIFIER(X) X::
#define OPEN_NS(X)   namespace X 
#define CLOSE_NS(X)  
// Helper function
#define QUALIFIED(NAME,...) FOR_EACH(QUALIFIER,__VA_ARGS__)NAME

// Emit some code
QUALIFIED(MyFoo,Outer,Next,Inner)  foo();

FOR_EACH(OPEN_NS,Outer,Next,Inner)
  class Foo;
FOR_EACH(CLOSE_NS,Outer,Next,Inner)

cpp foo.h 生成:

Outer::Next::Inner::MyFoo foo();

namespace Outer namespace Next namespace Inner 
   class Foo;

【讨论】:

我需要将GET_MACRO 的定义更改为GET_MACRO(__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1,)(action,__VA_ARGS__)。注意多余的逗号。如果没有这个,将宏应用于具有单个参数的列表,我会收到warning: ISO C99 requires rest arguments to be used。除此之外,很棒的宏! 太棒了,你应该得到考古学家的徽章! 对于那些尝试使用 msvc(此处为 2015)的人,这需要稍作修改,因为 msvc 不会将 __VA_ARGS__ 扩展为多个参数,即当 __VA_ARGS__a,b,c 时,FOO(X, __VA_ARGS__) 变为FOO(X, (a,b,c)) 而不是 FOO(X, a, b, c)。解决方案在这里:***.com/questions/5134523/… - 简而言之,GET_MACRO(__VA_ARGS__, ...)(action,__VA_ARGS__) 需要重写为 EXPAND(GET_MACRO(__VA_ARGS__, ...)(action,__VA_ARGS__))FE_X 也需要包含在 EXPAND(...) 宏中。 GET_MACRO 调用中,在FE_1 之后添加一个逗号,以防止Wgnu-zero-variadic-macro-arguments 调用宏时仅使用1 项进行迭代。【参考方案3】:

如果您的结构是用X-Macros 描述的,那么可以编写一个函数或一个宏来迭代结构的所有字段并打印它们的偏移量。

#include <stddef.h>   // offsetof macro

//--- first describe the structure, the fields, their types
#define X_FIELDS \
    X(int,    field1) \
    X(int,    field2) \
    X(char,   field3) \
    X(char *, field4)

//--- define the structure, the X macro will be expanded once per field
typedef struct 
#define X(type, name) type name;
    X_FIELDS
#undef X
 mystruct;

//--- "iterate" over all fields of the structure and print out their offset
void print_offset(mystruct *aStruct)

#define X(type, name) printf("offset of %s is %d\n", #name, offsetof(mystruct, name));
        X_FIELDS
#undef X


//--- demonstrate
int main(int ac, char**av)

    mystruct a =  0, 1, 'a', "hello";
    print_offset(&a);

    return 0;

【讨论】:

它只是混淆了结构的声明和打印偏移量的函数,但当您知道 X() 宏的效果时,并没有那么多。但好处是当你必须向结构中添加一个新字段时,你只有一个地方可以修改,即 X_FIELDS 宏。重新编译,print_offset() 函数将打印新字段的偏移量。不要重复自己! 并且仅适用于结构是你的并且你愿意混乱(恕我直言)它的定义 我只是在我想拥有一个枚举并通过名称访问枚举元素的情况下使用了这种方法。它确实混淆了代码,但使最终用户体验更好且不受限制。 我做了类似的事情,但除了最后一个 X 之外,毕竟使用了无参数的 Y 宏,以允许在某些情况下需要在项目之间使用分隔符而不是之后的终止符每个。 我不知道这种技术有一个名字和一个***页面!我使用 X 宏的方式太频繁了!【参考方案4】:

Gregory Pakosz 的解决方案效果很好。但我有两个小问题:

    使用 pedantic 选项编译时,我收到警告:“ISO99 需要使用其余参数”。 这是由第一个 FOR_EACH_1 宏中的可变参数引起的。删除这些并在 FOR_EACH_2 中更改对 FOR_EACH_1 的调用删除了此警告。

    #define FOR_EACH_1(what, x) 
    #define FOR_EACH_2(what, x, ...)\
        what(x);                    \
        FOR_EACH_1(what);
    

    由于我以一种非常通用的方式使用它,我有时不得不只使用 1 个参数调用重复宏。 (我知道重复一个项目 1 次是没有意义的;))。幸运的是,这个问题的解决方案非常简单。只需从 FOR_EACH 宏中删除 x 参数即可。

    #define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
    

这里有两个变化的完整清单:

#define CONCATENATE(arg1, arg2)   CONCATENATE1(arg1, arg2)
#define CONCATENATE1(arg1, arg2)  CONCATENATE2(arg1, arg2)
#define CONCATENATE2(arg1, arg2)  arg1##arg2

#define FOR_EACH_1(what, x)         \
    what(x)

#define FOR_EACH_2(what, x, ...)    \
    what(x);                        \
    FOR_EACH_1(what, __VA_ARGS__);

#define FOR_EACH_3(what, x, ...)    \
    what(x);                        \
    FOR_EACH_2(what, __VA_ARGS__);

#define FOR_EACH_4(what, x, ...)    \
    what(x);                        \
    FOR_EACH_3(what,  __VA_ARGS__);

#define FOR_EACH_5(what, x, ...)    \
    what(x);                        \
    FOR_EACH_4(what,  __VA_ARGS__);

#define FOR_EACH_6(what, x, ...)    \
  what(x);                          \
  FOR_EACH_5(what,  __VA_ARGS__);

#define FOR_EACH_7(what, x, ...)    \
    what(x);                        \
    FOR_EACH_6(what,  __VA_ARGS__);

#define FOR_EACH_8(what, x, ...)    \
    what(x);                        \
    FOR_EACH_7(what,  __VA_ARGS__);

#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
#define FOR_EACH_NARG_(...) FOR_EACH_ARG_N(__VA_ARGS__) 
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N 
#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0

#define FOR_EACH_(N, what, ...) CONCATENATE(FOR_EACH_, N)(what, __VA_ARGS__)
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)

【讨论】:

【参考方案5】:

也许使用可变参数作为数组初始化器,并迭代 countof(array)?即 sizeof(array)/sizeof(array[0])。该数组可能是 C99 匿名数组。

我想不出另一种方法来迭代宏的 var-args,因为我不知道如何对每个 var-arg 元素的文本做任何事情。 var-arg 部分也可能是一个包含逗号的单个参数,因为您可以使用 CPP、AFAIK 对它执行所有操作。

但这是我对 var-args 进行迭代的想法:

#define countof(a) ( sizeof(a)/sizeof((a)[0]) )
#define MACRO(fd, format, ...) do  int ar_[] =  __VA_ARGS__ ; \
for(int i=0; i<countof(ar_) ; ++i) \
    fprintf(fd, format, ar_[i]); \
  while(0)

【讨论】:

很抱歉,我看不到这个 sn-p 是如何回答这个问题的。首先,代码错过了countof 的定义,尽管您在第一段中给出了它。那么它应该是int ar_[]。最后,它仅在使用int 参数的可变参数列表调用宏时才有效;像这样MACRO(stdout, "%d", 1, 2, 3) 显然您需要调整宏以适应您的情况。您可以将类型设为宏参数之一。不过,感谢您找到丢失的 []。 不过,使用这个数组意味着两件事:通过变量参数列表传递的所有参数必须是相同类型(在你的情况下为int)并且必须有一个公共复制构造函数 是的,这有很大的限制。我并不是说这是一个好的或普遍有用的答案!不过,这可能是您在 C99 / GNU C 中可以做的最好的事情。在 C++ 中,你可以用模板来代替吗? 查看 ***.com/questions/1872220/… 了解 c++0x 版本【参考方案6】:

这是我能想到的最好的,使用标准 C:

#include <stddef.h>
#include <stdio.h>

// prints a single offset
#define PRN_STRUCT_OFFSET(x, a) printf("&" #x "." #a " = %d\n", offsetof(x, a));

// prints a struct with one member
#define PRN_STRUCT_OFFSETS_1(x, a) PRN_STRUCT_OFFSET(x, a)

// prints a struct with two members
#define PRN_STRUCT_OFFSETS_2(x, a, b) \
            PRN_STRUCT_OFFSET(x, a) \
            PRN_STRUCT_OFFSET(x, b)

// and so on until some N.
// Boost.Preprocessor might help here, I'm not sure

struct some_struct

    int a;
    void* c;
;

int main(void)

    PRN_STRUCT_OFFSETS_2(struct some_struct, a, c);

    return 0;

【讨论】:

实际上,我误读了这个问题,我以为他想输出 values,而不是 offsets 但事实并非如此;)跨度> 【参考方案7】:

我将此添加为另一个答案。这是使用 G++ 4.5.0 编译的 C++0x 的尝试

#include <iostream>
using namespace std;

template<typename L>
inline void for_each(L l)



template<typename L, typename P, typename... Q>
inline void for_each(L l, P arg, Q... args)

  l(arg);
  for_each(l, args...);


int main()

  for_each([] (int x)  cout << x; , 1, 2, 3);

  return 0;

程序打印

123

但是,使用这种方法,您传递给 lambda 表达式的所有参数都需要具有相同的类型,在上面的示例中为 int。但是,lambdas 允许您捕获以下变量:

int main()

  int offset = 10;

  for_each([offset] (int x)  cout << offset + x << endl; , 1, 2, 3);

  return 0;

打印出来的:

11
12
13

【讨论】:

宏观方法如何? 不是。对于那些使用 C++11 并愿意避免使用宏的人来说,这是一个替代方案。宏观解决方案是公认的答案。 另外,当时我认为问题中没有C标签 如果我想将STD(cout, endl, cin) 的宏用于扩展为using std::cout; using std::endl; using std::cin;,我看不出如何使用模板实现这种宏扩展。【参考方案8】:

要启用一个空的__VA_ARGS__,可以使用GNU 扩展##_VA_ARGS__ https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html

【讨论】:

【参考方案9】:

如果您的目标是 Objective-C…请查看 AWESOME KSVarArgs on Github

KSVarArgs 是一组宏,旨在简化 Objective-C 中可变参数的处理。所有宏都假定可变参数列表仅包含objective-c 对象或类似对象的结构(可分配给类型id)。基本宏 ksva_iterate_list() 迭代变量参数,为每个参数调用一个块,直到遇到终止的 nil。其他宏是为了方便转换为常用集合。

/*! @param firstNote NSString that is the only known arg 
 */

- (void) observeWithBlocks:(NSString*)firstNote,...

  /*! ksva_list_to_nsarray puts varargs into 
      new array, `namesAndBlocks` 
   */
  ksva_list_to_nsarray(firstNote, namesAndBlocks);

  /// Split the array into Names and Blocks

  NSArray *names = [namesAndBlocks subArrayWithMembersOfKind:NSString.class],
     *justBlocks = [namesAndBlocks arrayByRemovingObjectsFromArray:names];

  [names eachWithIndex:^(id obj, NSInteger idx) 
     [self observeName:obj usingBlock:^(NSNotification *n)     
        ((void(^)())justBlocks[idx])(n);
     ];    
  ];

示例用法:

[NSNotificationCenter.defaultCenter observeWithBlocks: 
  NSViewFrameDidChangeNotification, /// first, named arg
  ^(NSNotification *m) [self respondToFrameChange]; , // vararg
  NSTextViewDidChangeSelectionNotification, // vararg
  ^(NSNotification *z) [z.infoDict[@"textView"] save]; , // vararg
  nil // must nil-terminate
]; 

【讨论】:

以上是关于是否可以迭代可变参数宏中的参数?的主要内容,如果未能解决你的问题,请参考以下文章

可变宏中令牌的连接

libreoffice calc - 宏中的可变参数

在指向另一个宏的可变参数宏中为每个参数添加前缀

找出可变参数宏中__VA_ARGS__的类型

是否可以将可变数量的参数传递给redshift中的存储过程?

用于定义具有可变参数的函数的嵌套#define 问题