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