【问题标题】:Confusion about C macro expansion in enum关于枚举中的 C 宏扩展的困惑
【发布时间】:2016-09-06 06:35:42
【问题描述】:

我在fwts 代码库中看到下面的代码 sn-p:

#define FWTS_CONCAT(a, b) a ## b
#define FWTS_CONCAT_EXPAND(a,b) FWTS_CONCAT(a, b)
#define FWTS_ASSERT(e, m)   \
enum { FWTS_CONCAT_EXPAND(FWTS_ASSERT_ ## m ## _in_line_, __LINE__) = 1 / !!(e) }

#define FWTS_REGISTER_FEATURES(name, ops, priority, flags, features)    \
/* Ensure name is not too long */                   \
FWTS_ASSERT(FWTS_ARRAY_LEN(name) < 16,                  \
    fwts_register_name_too_long);   

我的问题是:

    1234563导致1/0 ?
  • 顺便说一句,FWTS_CONCAT_EXPAND(a,b)FWTS_CONCAT(a, b) 似乎是重复的,为什么我们需要两个?

添加 1

根据@Klas Lindbäck的回答,我想通过一个具体的例子来进行宏扩展。

假设我有:

#define M_1  abc
#define M_2  123

那我FWTS_CONCAT_EXPAND(M_1,M_2)的展开过程应该是:

FWTS_CONCAT_EXPAND(M_1,M_2)
->
FWTS_CONCAT(abc, 123)
->
abc123

如果我直接申请FWTS_CONCAT(M_1, M_2),会不会扩成这样?

FWTS_CONCAT(M_1, M_2)
->
M_1M_2
-> 
Bang! M_1M_2 is an invalid symbol!

(如果我错了,请纠正我......)

添加 2

试过gcc -E macroTest.c -o macroTest.i:

(macroTest.c)

#define M_1  abc
#define M_2  123
#define FWTS_CONCAT(a, b) a ## b
#define FWTS_CONCAT_EXPAND(a,b) FWTS_CONCAT(a, b)

FWTS_CONCAT_EXPAND(M_1, M_2)
FWTS_CONCAT(M_1,M_2)

(macroTest.i)

# 1 "macroTest.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "macroTest.c"

abc123
M_1M_2

我想我明白了宏扩展规则的意义。下面是一些相关的概念和引用:

Argument Prescan:

宏参数完全宏扩展之前 替换到宏体中,除非它们被字符串化粘贴 与其他代币。替换后,整个宏体, 包括被替换的参数,再次扫描宏 扩大。结果是参数被扫描两次以展开 宏调用它们。

Stringification

当宏参数与前导“#”一起使用时,预处理器 用实际参数的文字替换它,转换为 字符串常量

Token Pasting / Token Concatenation:

在扩展时将两个标记合并为一个通常很有用 宏。这称为令牌粘贴令牌连接。这 '##' 预处理运算符执行令牌粘贴。当一个宏是 展开后,每个“##”运算符两侧的两个标记是 组合成一个标记,然后替换“##”和两个 宏扩展中的原始标记。

所以我的场景的详细流程是这样的:

FWTS_CONCAT_EXPAND(M_1, M_2)   
-> FWTS_CONCAT_EXPAND(abc, 123) // M_1, M_2 pre-expanded since FWTS_CONCAT_EXPAND has no ##.
-> FWTS_CONCAT(abc, 123) // FWTS_CONCAT_EXPAND expanded into FWTS_CONCAT
-> abc123  // FWTS_CONCAT expanded

FWTS_CONCAT(M_1,M_2)
-> M_1M_2 //M_1, M_2 are not pre-expanded because of the ## in FWTS_CONCAT
-> DEADEND

【问题讨论】:

  • 我认为它应该会导致错误。
  • 它很可能会导致编译错误,因为无法计算常量1/0
  • 谢谢。但我就是不明白为什么要定义 enum 类型来引发编译时错误? enum 有什么特别之处吗?
  • @smwikipedia:除了它的值必须是恒定的(并且有单行错误输出,给出标识符的抱怨)?我已经看到对数组大小进行了类似操作,但这不会(甚至意外地)为该项目分配任何存储空间。
  • enum 在这方面没什么特别的;我使用了typedef 具有类似技巧的数组。关键是当预处理器不起作用时,我们使用 C 构造执行编译时检查。例如,您不能在#if 语句中使用sizeof 来测试数组大小。

标签: c


【解决方案1】:

宏用于编译时检查。当您编写将在许多不同平台上编译和运行且某些平台可能不兼容的代码时,这很有用。

如果FWTS_ASSERT 的第一个参数的计算结果为非零(真),则!!(e) 的计算结果将为 1,并且将创建名为 FWTS_ASSERT_&lt;second parameter&gt;_in_line_&lt;line&gt; 的枚举。我怀疑枚举从未真正使用过。

如果FWTS_ASSERT 的第一个参数的计算结果为0 (= false),那么编译器将尝试计算1/0 并生成一个编译器错误,它有望在这种情况下告诉哪个枚举成员导致了错误FWTS_ASSERT_fwts_register_name_to_long_in_line_4.

顺便说一句,FWTS_CONCAT_EXPAND(a,b) 和 FWTS_CONCAT(a, b) 似乎是重复的,为什么我们需要两个?

FTW_CONCAT_EXPAND 分两步完成,因为我们想首先展开参数中的任何宏,然后执行连接。分两步进行,使得预处理器在进行字符串连接之前对参数进行宏扩展。

【讨论】:

  • @smwikipedia 找出答案的好方法是自己尝试一下! ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-09
  • 2015-04-25
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多