2016-12-02 94 views
0

我有很多行代碼像下面的參數列表:C代碼宏:如何使用宏來產生的另一個宏

 sp_setup_point(setup, get_vert(vertex_buffer, i-0, stride)); 

爲他們每個人,我希望能夠提取(I- 0)並將其傳遞給另一個函數。像:

 sp_setup_point(setup, get_vert(vertex_buffer, i-0, stride)); 
    my_test_func(i-0); 

,所以我寫了兩個宏:

 #define GET_VERT(_x, _y, _z) get_vert(_x, _y, _z) , _y 
     #define SP_SETUP_POINT(_x, _y, _z) sp_setup_point(_x, _y); my_test_func(_z); 

,並呼籲他們喜歡:

 SP_SETUP_POINT(setup, GET_VERT(vertex_buffer, i-0, stride)); 

但是,它並沒有給我想要的東西,它擴展爲:

 sp_setup_point(setup, get_vert(vertex_buffer, i-0, stride), i-0); my_test_func(); 

和MSVC編譯器抱怨

not enough actual parameters for macro 'SP_SETUP_POINT' 

我搜索相當多,根據https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html

宏參數是完全宏擴展它們代入宏體之前,除非它們是字符串化或與其他標記粘貼。替換之後,將再次掃描整個宏體(包括替換參數)以擴展宏。結果是參數被掃描兩次以擴展其中的宏調用

參數已完全展開,但附加參數未被識別。那是怎麼回事?任何建議表示讚賞。

+0

提示:使用您的編譯器選項來發出預處理的源代碼,以查看錶達式的計算結果。 – doynax

+0

是的,我試過了。我經過預處理後得到了代碼,但仍不知道如何獲得所需的宏擴展。 – luckyyang

回答

1

究其原因,IIRC,是GET_VERT 內的實際參數列表只得到擴大後封閉宏調用掃描。因此,PP首先要在SP_SETUP_POINT參數列表的末尾看到「)」。在這個過程中,它認識到它是一個短於聲明形式的參數。一種間接的方式有助於C語言的宏編程,但要小心,這種編程方法很奇怪,並且因早期糟糕的實現選擇而受到影響,這是由於大量兼容性債務而無人敢於糾正的。

#define GET_VERT(_x, _y, _z) get_vert(_x, _y, _z) , _y 
#define SP_SETUP_POINT(_x, expandme) SP_SETUP_POINT_(_x,expandme) 
#define SP_SETUP_POINT_(_x,_y,_z) sp_setup_point(_x, _y); my_test_func(_z) 

最後一種形式更好寫成

#define SP_SETUP_POINT_(_x,_y,_z) do{ sp_setup_point(_x, _y); my_test_func(_z); }while(0) 

因爲寫SG。像

if (a==1) SP_SETUP_POINT(setup, GET_VERT(vertex_buffer, i-0, stride)); 

會導致控制流程中的不可見錯誤。

+1

感謝您的詳細anwser。我也想過,但是沒有這樣的運氣。這裏是我的版本: '#定義SP_SETUP_POINT(...)SP_SETUP_POINT_IMPL(__ __ VA_ARGS)' '#定義SP_SETUP_POINT_IMPL(_x,_y,_z)sp_setup_point(_x,_y); \t my_test_func(_z)' – luckyyang

1

如果我要解決這個問題,我想我會從所需的輸出開始:

sp_setup_point(setup, get_vert(vertex_buffer, i-0, stride)); 
my_test_func(i-0); 

你有4個參數:setupvertex_bufferexpr,並stride,其中expri-0參數(這本身就是一個奇怪的符號;這與i有什麼不同?)。我可能會在生產質量宏中使用這些更長的名稱;我打算使用短名稱a ..d在這裏。

所以,我會從起點設計我的宏:

#define SP_SETUP_POINT(a, b, c, d) \ 
    do { sp_setup_point((a), get_vert((b), (c), (d))); \ 
     my_test_func(c); } while (0) 

然後,您可以調用:

SP_SETUP_POINT(setup, vertex_buffer, i-0, stride); 

這將產生你想要的代碼,即使在上下文中如:

if (x > y) 
    SP_SETUP_POINT(setup, vertex_buffer, i-0, stride); 
else 
    SP_SETUP_POINT(setup, vertex_buffer, i+2, stride); 

如果不使用do { … } while (0)符號,那麼你必須使用逗號運愛適易分離的函數調用:

#define SP_SETUP_POINT(a, b, c, d) \ 
    (sp_setup_point((a), get_vert((b), (c), (d))), \ 
    my_test_func(c)) 

這取值爲my_test_func()返回值。如果您不需要測試來自sp_setup_point()的返回值,例如如果它無論如何返回void - 如果my_test_func()返回void沒有問題。

您還可以修改宏調用MY_TEST_FUNC(c),然後有條件地定義它:

#ifdef CALL_MY_TEST_FUNCTION 
#define MY_TEST_FUNC(c) my_test_func(c) 
#else 
#define MY_TEST_FUNC(c) ((void)(c)) 
#endif 

評估中的「不叫」情況c的優點是編譯器可以確保c仍保持有效的代碼改變。不要低估長壽命代碼中的好處。

+1

感謝您的另一個觀點。如果我無法弄清楚宏觀的魔力。我將採用這種方式,它需要原始代碼的大量入侵式更改,所以我並不把它當成最佳解決方案。 – luckyyang