我有很多代码如下:
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 :(得分: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 :(得分:1)
如果我要解决这个问题,我想我会从所需的输出开始:
sp_setup_point( setup, get_vert(vertex_buffer, i-0, stride) );
my_test_func(i-0);
您有4个参数:setup
,vertex_buffer
,expr
和stride
,其中expr
是i-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
即使在代码更改时仍然有效。不要低估长期代码中的好处。