变量函数名称`##`语法的局限性

时间:2014-09-23 21:04:01

标签: macros syntax portability c-preprocessor

偶尔,C / C ++语法让我大吃一惊,今天就是其中之一。我偶然发现了一些代码,这些代码使用带有##的宏来为特定的硬件寄存器生成独特的单独访问器方法(如下所示)。

问题##语法存在哪些限制或疑虑?

static uint8_t write(uint16_t _addr, uint8_t _data);  // Implemented elsewhere
static uint8_t read(uint16_t addr);                   // Implemented elsewhere

#define __GP_REGISTER8(name, address)             \
  static inline void write##name(uint8_t _data) { \
    write(address, _data);                        \
  }                                               \
  static inline uint8_t read##name() {            \
    return read(address);                         \
  }

__GP_REGISTER8 (A, 0x0000);    // Register A
__GP_REGISTER8 (B, 0x0001);    // Register B
__GP_REGISTER8 (C, 0x0002);    // Register C

#undef __GP_REGISTER8

实现上述宏后,您现在可以进行以下调用:

writeA(0xA5);
writeB(0x5A);
uint8_t c_value = readC();

##如何完全正常工作(即仅在宏等中)?此外,人们不使用它,因为它没有真正提供任何优势,它是更多的工作,或使用这种技术还有其他副作用/缺点?

1 个答案:

答案 0 :(得分:2)

这(几乎)不是关于C ++语法,而是关于C预处理器语言。该功能称为令牌粘贴连接。来自GCC manual

  

##预处理运算符执行标记粘贴。扩展宏时,每个##运算符两侧的两个标记将合并为一个标记,然后在宏扩展中替换##和两个原始标记。

所以在你的(简化)例子中,

__GP_REGISTER8(A, 0x0000)

将(通过字符串替换)扩展为

static inline void write ## A(uint8_t _data) { write(0x0000, _data); }

然后,预处理器会将write ## A三元组替换为您writeA的{​​{1}}。

  

##语法有哪些限制或疑虑?

肮脏的宏观诡计可能会让人们感到困惑。限制非常明确:您可以执行令牌粘贴 - 没有更多,仅此而已。让我再次强调,所有这些都发生在之前 C或C ++编译器看到代码。因此,如果你用无意义的参数“调用”这样一个宏,预处理器仍然会愉快地扩展它,然后编译器才会看到垃圾并输出一些帮助不大的错误消息。也就是说,如果在头文件中使用宏来清理重复声明,那可能就好了。

随着C ++模板元编程的不断增强,这些技巧已经失去了很多重要性。可以合理地说,使用模板化函数(例如

)可以同样很好地实现使用您发布的宏实现的功能。
enum class Registers { A = 0x0000, B = 0x0001, C = 0x0002; };

template<Registers R>
void
write(uint16_t data)
{
    write(static_cast<int>(R), data);
}

然后用作

write<Registers::A>(0xA5);