是否有可能在可变参数宏中迭代参数?

vsh*_*noy 66 c foreach c99 variadic c-preprocessor

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

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

像这样的东西:

struct a {
    int a;
    int b;
    int c;
};

/* PRN_STRUCT_OFFSETS will print offset of each of the fields 
   within structure passed as the first argument.
*/

int main(int argc, char *argv[])
{
    PRN_STRUCT_OFFSETS(struct a, a, b, c);

    return 0;
}

Gre*_*osz 65

这是我今天的作业,它基于宏观技巧,今天我特别了解__VA_NARG__了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;
}
Run Code Online (Sandbox Code Playgroud)

打印出:

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
Run Code Online (Sandbox Code Playgroud)

编辑:这是一个略有不同的版本,试图更通用.FOR_EACH(what, ...)宏适用what于变量参数列表中的每个其他参数.

所以,你只需要定义一个宏,它接受一个像这样的参数:

#define DO_STUFF(x) foo(x)
Run Code Online (Sandbox Code Playgroud)

这将应用于列表中的每个参数.所以,对于你的典型例子,你需要破解一点,但它仍然保持简洁:

#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)
Run Code Online (Sandbox Code Playgroud)

你这样应用它:

FOR_EACH(PRN_STRUCT_OFFSETS, a, b, c);
Run Code Online (Sandbox Code Playgroud)

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

  • 整齐.我想知道是否可以通过将__VA_ARGS__传递给另一个具有命名参数的宏来分割__VA_ARGS__,以便捕获__VA_ARGS__中的一个,所以我喜欢这个答案.太糟糕的CPP使您为每个计数编写宏,而不是允许递归扩展并在没有args时执行不同的操作.我不知道我是否曾经包含那么大的宏集合,除非它会在某处保存很多代码.好吧,也许是我自己在开发过程中使用的......无论如何,整洁的技巧. (2认同)
  • 我已经看到双字符串在http://stackoverflow.com/questions/2751870/how-exactly-does-the-double-stringize-trick-work中是如何工作的,但为什么STRINGIZE和CONCATENATE三个调用深? (2认同)

Mar*_*vin 41

冒着获得考古学家徽章的风险,我认为格雷戈里上面的回答使用了从参数数量上重载宏的技术有一点改进

用foo.h:

// Make a FOREACH macro
#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(_1,_2,_3,_4,_5,NAME,...) NAME 
#define FOR_EACH(action,...) \
  GET_MACRO(__VA_ARGS__,FE_5,FE_4,FE_3,FE_2,FE_1)(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)
Run Code Online (Sandbox Code Playgroud)

cpp foo.h生成:

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

namespace Outer {namespace Next {namespace Inner {
   class Foo;
}}}
Run Code Online (Sandbox Code Playgroud)

  • 我需要将`GET_MACRO`的定义更改为`GET_MACRO(__ VA_ARGS __,FE_5,FE_4,FE_3,FE_2,FE_1,)(动作,__ VA_ARGS __)`.注意额外的逗号.如果没有这个,将宏应用于具有单个参数的列表,我会收到`警告:ISO C99需要使用休息参数.除此之外,伟大的宏! (3认同)
  • 对于那些试图用这个MSVC(2015年在这里),这需要稍微修改,因为MSVC不扩张`__VA_ARGS__`成多个参数,即当`__VA_ARGS__`是'A,B,C`,`FOO(X,__VA_ARGS __)`变成'FOO(X,(a,b,c))`而不是`FOO(X,a,b,c)`.该解决方案是在这里:http://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly - 简而言之,`GET_MACRO(__ VA_ARGS__,...)(动作,__ __ VA_ARGS)`必须rewriten如`EXPAND(GET_MACRO(__ VA_ARGS__,...)(动作,__ VA_ARGS __))`和`FE_X`需要在`EXPAND(...)`宏被包装为好. (3认同)
  • 在“ GET_MACRO”调用中,如果仅用1项进行宏调用,则在“ FE_1”之后添加逗号以防止“ Wgnu-zero-variadic-macro-arguments”。 (2认同)

phi*_*ant 15

如果使用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;
}
Run Code Online (Sandbox Code Playgroud)

  • 它只会模糊结构的声明和打印偏移的函数,但是当你知道X()宏的效果时,它不会那么多.但优点是,当您必须向结构添加新字段时,您只需要修改一个位置X_FIELDS宏.重新编译,print_offset()函数将打印新字段的偏移量.不要重复自己! (2认同)
  • 我只是在我希望拥有枚举并且可以按名称访问枚举元素的情况下使用这种方法.它确实模糊了代码,但使最终用户体验更好,没有约束. (2认同)

Iqo*_*qon 6

Gregory Pakosz的解决方案运作良好.但我有两个小问题:

  1. 使用迂腐选项进行编译时,我收到了警告:"ISO99要求使用其他参数".这是由第一个FOR_EACH_1宏中的variad参数引起的.删除这些并在FOR_EACH_2中更改对FOR_EACH_1的调用已删除此警告.

    #define FOR_EACH_1(what, x) 
    #define FOR_EACH_2(what, x, ...)\
        what(x);                    \
        FOR_EACH_1(what);
    
    Run Code Online (Sandbox Code Playgroud)
  2. 由于我以非常通用的方式使用它,我有时只需要用一个参数调用repeat宏.(我知道重复一次项目是没有意义的;)).幸运的是,这个问题的解决方案非常简单.只需从FOR_EACH宏中删除x参数.

    #define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
    
    Run Code Online (Sandbox Code Playgroud)

这里包含两个更改的完整列表:

#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__)
Run Code Online (Sandbox Code Playgroud)


Pet*_*des 5

也许使用varargs作为数组初始化器,并遍历countof(array)?即sizeof(array)/ sizeof(array [0])。该阵列可能是C99匿名阵列。

我想不出另一种方法来遍历宏的var-args,因为我不知道如何对每个var-arg元素的文本执行任何操作。var-arg部分也可能是其中包含逗号的单个参数,因为您可以使用CPP,AFIAK对它进行全部处理。

但是这是我遍历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)
Run Code Online (Sandbox Code Playgroud)

  • 仍然,使用此数组意味着两件事:通过变量参数列表传递的所有参数必须具有相同的类型(在您的情况下为int),并且必须具有公共副本构造函数 (2认同)