2009-12-09 65 views
54

我想知道是否有可能迭代傳遞給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; 
} 

回答

54

這是我一天的功課,它是基於宏觀的技​​巧,今天我特別瞭解__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; 
} 
+0

我希望回答的長度不會讓你太過驚慌 – 2009-12-09 20:37:24

+2

整潔。我想知道是否可以通過將__VA_ARGS__傳遞給另一個具有命名參數來捕獲__VA_ARGS__之一的宏來分割它,所以我喜歡這個答案。太糟糕的CPP讓你爲每個計數寫宏,而不是允許遞歸擴展,並在沒有參數時做不同的事情。我不知道我是否會包含那麼大的宏集合,除非它會在很多地方保存代碼。那麼,也許是爲了我自己在開發過程中使用...無論如何,整潔的伎倆。 – 2009-12-09 21:03:14

+1

這是一個不錯的把戲格里高利。當使用Google搜索時,我偶然發現了__VA_NARG__帖子,但不知道(或者是無知),您可以使用它根據參數數目構建調度程序宏。你是我最初採取的方法。 phillipe,X-Macros是一個有趣的方法。 感謝你們所有人的迴應。 – vshenoy 2009-12-10 05:48:30

0

這是我能想到的最好的,用標準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; 
} 
+0

其實,我誤解了問題,我還以爲他要輸出_values_,而不是_offsets_但事實並非如此; ) – catchmeifyoutry 2009-12-09 07:55:05

+0

你還有一個有效的點:)只是不是你想的。 – GManNickG 2009-12-09 07:56:00

10

如果你的結構與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; 
} 
+0

哇混淆了很多的代碼 – 2009-12-09 10:29:55

+1

它只是混淆了結構的聲明和打印補償的函數,但是當你知道X()宏的效果時,它並沒有那麼多。但優點是,當你必須在結構中添加一個新字段時,你只有一個地方可以修改X_FIELDS宏。重新編譯和print_offset()函數將打印新字段的偏移量。不要重複自己! – philant 2009-12-09 11:50:30

+0

,只適用於如果結構是你的並且你願意混亂(imho)它的定義 – 2009-12-09 12:14:21

3

也許使用可變參數作爲數組初始值設定項,並遍歷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) 
+0

我很抱歉,但我沒有看到這段代碼如何回答這個問題。首先,代碼忽略了「countof」的定義,儘管您在第一段中給出了它。那麼它應該是'int ar _ []'。最後它只會在調用具有'int'參數的可變參數列表的宏時才起作用;像這樣'MACRO(stdout,「%d」,1,2,3)' – 2009-12-09 15:31:30

+0

顯然你需要調整宏以適應你的情況。你可以創建一個宏參數。不過,感謝您捕捉失蹤的[]。 – 2009-12-09 18:31:01

+2

還有,這個數組的使用意味着兩件事:通過變量參數列表傳遞的所有參數需要是相同類型的(在你的情況下是'int'),並且必須有一個公共拷貝構造函數 – 2009-12-09 18:48:19

0

我將此添加爲另一個答案。這是一個嘗試在使用C++ 0x中這樣做,與G ++ 4.5.0

#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; 
} 

編譯的程序打印

但是,使用這種方法,所有的傳遞給lambda表達式的參數需要具有相同的類型,上例中爲int。然而,lambda表達式讓你捕捉變量一樣:

int main() 
{ 
    int offset = 10; 

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

    return 0; 
} 

打印出:

11 
12 
13 
+0

這是一個宏觀方法? – marsh 2015-04-02 13:53:28

+1

不是。對於那些使用C++ 11並願意避免使用宏的人來說,這是一種選擇。宏觀解決方案是被接受的答案。 – 2015-04-02 14:30:46

+0

此外,當我認爲在 – 2015-04-16 09:21:35

36

在賺取考古學家徽章的風險,我覺得這是一個小的改進,格雷戈裏的回答以上使用技術從Overloading Macro on Number of Arguments

隨着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) 

CPP foo.h中產生:

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

namespace Outer {namespace Next {namespace Inner { 
    class Foo; 
}}} 
+2

問題中沒有C標記時,我需要將'GET_MACRO'的定義更改爲'GET_MACRO(__ VA_ARGS __,FE_5,FE_4,FE_3,FE_2,FE_1,)(action,__ VA_ARGS __)' 。注意額外的逗號。如果沒有這個,將宏應用於帶有單個參數的列表,我會收到'warning:ISO C99需要使用其餘參數。除此之外,偉大的宏! – wyer33 2015-03-03 21:08:54

+0

這真是太好了,你應該得到一個考古學家的徽章! – pm89 2016-07-09 19:15:29

+2

對於那些試圖使用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__,...)(action,__ VA_ARGS __)'必須是重寫爲EXPAND(GET_MACRO(__VA_ARGS__,...)(action,__VA_ARGS__)),並且'FE_X'也需要被包裝在一個'EXPAND(...)'宏中。 – GaspardP 2016-12-20 15:08:39

2

這是我這
享受

#include <stdlib.h> 
#include <stdio.h> 

#define ITERATE_OVER_VARADICT_MACROS(str, ...)\ 
do{\ 
    int i, _arr_[] = {__VA_ARGS__};\ 
    fprintf(stderr,"msg =%s\n", str); \ 
    for(i=0; i<sizeof(_arr_)/sizeof(int) ; i++){ \ 
    fprintf(stderr,"_arr_[%d]= %d\n", i, _arr_[i]); \ 
    }\ 
}while(0) 


int main(int argc, char* argv[]) 
{ 
    ITERATE_OVER_VARADICT_MACROS("Test of iterate over arguments in variadic macros", 10,12, 34); 
    return 0; 
} 
+0

很酷,但是這並沒有回答這個問題(它不是一個整數列表,它是一個struct字段列表) – 2013-12-30 21:30:02

+2

這是怎麼得到四票?它沒有回答這個問題,與2009年12月Peter Cordes的答案相同。 – 2015-07-27 16:08:39

0

如果你的目標Objective-C解決方案...檢查出真棒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 
]; 
1

格雷戈裏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); 
    
  2. 因爲我在一個非常通用的方式使用它,我有時不得不調用重複宏只有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__)