C预处理器:提前评估宏

13
考虑以下设置:

a.h

#define A 5
#define B A
#undef A
#define A 3

a.c

#include "a.h"
#include <stdio.h>

int main()
{
    printf("%d\n", B);
    return 0;
}

虽然这段代码可以输出3,但是有没有办法在a.h的第二行就将A替换成5,让它输出5呢?

1
我认为你做不到。我会等待比我更有知识的人来证明我错了。 - R Sahu
5
这些不是值的赋值,而是在使用时进行文本替换。 A 从未具有值5或3,而 B 也从未等于5或3。 - Weather Vane
@WeatherVane 感谢你提醒。我已经相应地编辑了措辞。 - user2037593
5
您可以考虑将a.h改成const int B = A;,而不是#define B A - 这样它就能在编译时捕获A的值。另一个选择是使用非标准但被广泛实现(如 gcc、clang、visual C++)的#pragma push_macro("A")来保留A的原始值,然后在计算B之前弹出它,但这种方法比较繁琐且容易出错。 - Tony Delroy
1
const int 可以使用。https://ideone.com/MpiOOz - Erobrere
显示剩余2条评论
2个回答

15

不,没有办法做到。除非你知道所有可能的A值,并且它们始终是整数,在这种情况下,您可以费力地逐个测试每个值:

#if A == 0
# define B 0
#elif A == 1
# define B 1
#elif A == 2
# define B 2
/*  ... and a very long etc. */
#endif
如果您的用例只涉及整数,则有更多选项。例如,您可以将B声明为static const intenum(取决于语言),而不是宏,这显然会使用宏的当前值。如果您真的非常想要宏,那么Boost预处理库具有上面繁琐的#if序列的实现(通过一些巧妙的处理,可将所需的预处理器语句数量减少到log(N)而不是N)。
#define预处理指令中不存在宏替换;这个事实在C标准(§6.10第7段)(带有相同措辞的C++标准的§16第6段)中已经说明:

预处理指令内的预处理记号不受宏扩展的影响,除非另有规定。

在描述#if#include指令时,标准规定确实进行了宏替换,这就是为什么上面的#if解决方案有效(以及同时使用计算的#include的Boost实现)。

7
是的。Boost的预处理库(一组可移植的包含文件,而不是扩展的预处理器)包括对“可变”宏定义的支持。您可以将宏定义为扩展到可变插槽的引用,而不是直接扩展为值,因此可以更改该值,因为它早期扩展了“分配”给它的值。在这种情况下,您对于能够更改值的能力不太感兴趣,而是因为这种早期扩展意味着它可以在使用B或重新定义A之前从A中抓取值。
#include <boost/preprocessor/slot/slot.hpp>

#define A 5
#define B BOOST_PP_SLOT(1)

// "assign" A to B
#define BOOST_PP_VALUE A
#include BOOST_PP_ASSIGN_SLOT(1)

#undef A
#define A 3

#include "a.h"
#include <stdio.h>

int main()
{
    printf("%d\n", B);  // 5
    return 0;
}

支持仅限于整数。它利用了#if指令强制展开任何包含的宏的事实(#line#error也是如此,尽管对于此目的不太有用),并使用它们来建立存储在隐藏后端宏中的等效整数值,用于分配给的插槽。这样,它可以从A中“提取”一个值,然后B即使A更改或删除,也可以引用该值本身。

3
#include BOOST_PP_ASSIGN_SLOT(1)... 这太扯了!我们不需要一个混淆的 C++ 代码竞赛,因为我们有 boost! - chqrlie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接