为什么要用C语言中的其他宏递归地定义宏?

Leg*_*ion 5 c arduino

我想看看arduino函数digitalWrite是如何工作的.但是当我查找函数的源代码时,它充满了宏,这些宏本身是根据其他宏定义的.为什么会以这种方式构建而不是仅使用函数?这只是糟糕的编码风格还是在C中做事的正确方式?

例如,digitalWrite包含宏digitalPinToPort.

#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )
Run Code Online (Sandbox Code Playgroud)

并且pgm_read_byte是一个宏:

#define pgm_read_byte(address_short)    pgm_read_byte_near(address_short)
Run Code Online (Sandbox Code Playgroud)

并且pgm_read_byte_near是一个宏:

#define pgm_read_byte_near(address_short) __LPM((uint16_t)(address_short))
Run Code Online (Sandbox Code Playgroud)

并且__LPM是一个宏:

#define __LPM(addr)         __LPM_classic__(addr)
Run Code Online (Sandbox Code Playgroud)

并且__LPM_classic__是一个宏:

#define __LPM_classic__(addr)   \
(__extension__({                \
    uint16_t __addr16 = (uint16_t)(addr); \
    uint8_t __result;           \
    __asm__ __volatile__        \
    (                           \
        "lpm" "\n\t"            \
        "mov %0, r0" "\n\t"     \
        : "=r" (__result)       \
        : "z" (__addr16)        \
        : "r0"                  \
    );                          \
    __result;                   \
}))
Run Code Online (Sandbox Code Playgroud)

与此没有直接关系,但我也认为双下划线只应由编译器使用.是否有正确的LPM前缀__

Bas*_*tch 6

如果您的问题是"为什么会使用多层宏?",那么:

  • 为什么不?特别是在没有C99之前的时代inline.一个典型的例子是20世纪80年代的时代getc这是(IIRC)在那个时候(SunOS3.2,1987)记录为宏,具有man页面告诉(我忘了细节),与一些FILE* filearr[];一个getc(filearr[i++])是错误的(IIRC中,当时不存在未定义的行为术语).当您查看某些系统标头(例如,<stdio.h>或其中包含的某些标头)时,您可以找到此类宏的定义.而在那个时候(与计算机上运行在几MHz,所以比现在更慢千倍)getc 必须是出于效率的考虑宏(因为inline不存在,和编译器确实没有间优化像他们现在能够做的).当然,您可以getc自己的宏中使用.
  • 即使在今天,一些标准也定义了宏.特别是今天的waitpid(2)系统调用文档WIFEXITEDWEXITSTATUS宏,并且你的#define一些宏混合它们是明智的.
  • 重点是理解C预处理器的工作原理及其深刻的文本(非常脆弱)性质.这在所有关于C的教科书中都有解释.因此,您需要了解幕后发生的事情.
  • 现代时代C(即C99和C11)的经验法则是系统地更喜欢将某些static inline功能(在某些头文件中定义)转换为等效的宏.换句话说,#define有些宏只有当你无法避免它时.并明确记录这一事实.
  • 几层宏可能(有时)提高代码的可读性.
  • 可以使用#ifdef macroname有时有用的宏进行测试.

当然,当你敢于定义几层宏(我不会称之为"递归",阅读自引用宏)时,你需要非常小心并理解所有这些宏的结果(组合和单独).查看预处理的表单很有帮助.

顺便说一下,为了调试复杂的宏,我有时会这样做

gcc -C -E -I/some/directory mysource.c | sed 's:^#://#:' > mysource.i
Run Code Online (Sandbox Code Playgroud)

然后我调查mysource.i,有时甚至必须编译它,也许是gcc -c -Wall mysource.i为了获得位于预处理形式的警告mysource.i(我可以在我的emacs编辑器中检查).该sed命令是"评论"的开始的行# 被设置源位置(点菜#line)....有时我连做indent mysource.i

(实际上,我对此有一个特殊规则Makefile)

LPM的前缀是__正确吗?

顺便说一句,名称以_(通过标准和传统方式)保留给实现开头.原则上,您不能以您的名字开头,_因此不会发生任何碰撞.

请注意,__LPM_classic__宏使用statement-expr扩展名 (GCCClang)

另请参阅其他编程语言.Common Lisp有一个非常不同的宏模型(更有趣的一个).阅读有关卫生宏的信息.我个人感到遗憾的是,C预处理器从未发展成为更强大的(和Scheme类似).为什么没有发生这种情况(想象一下C预处理器能够调用Guile代码进行宏扩展!)可能是出于社会和经济原因.

有时仍应考虑使用其他预处理器(如m4GPP)来生成 C代码.见autoconf.