2016-12-02 116 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