我想看看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前缀__?
如果您的问题是"为什么会使用多层宏?",那么:
inline.一个典型的例子是20世纪80年代的时代getc这是(IIRC)在那个时候(SunOS3.2,1987)记录为宏,具有注在man页面告诉(我忘了细节),与一些FILE* filearr[];一个getc(filearr[i++])是错误的(IIRC中,当时不存在未定义的行为术语).当您查看某些系统标头(例如,<stdio.h>或其中包含的某些标头)时,您可以找到此类宏的定义.而在那个时候(与计算机上运行仅在几MHz,所以比现在更慢千倍)getc 必须是出于效率的考虑宏(因为inline不存在,和编译器确实没有间优化像他们现在能够做的).当然,您可以getc在自己的宏中使用.WIFEXITED和WEXITSTATUS宏,并且你的#define一些宏混合它们是明智的.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扩展名
(GCC和Clang)
另请参阅其他编程语言.Common Lisp有一个非常不同的宏模型(更有趣的一个).阅读有关卫生宏的信息.我个人感到遗憾的是,C预处理器从未发展成为更强大的(和Scheme类似).为什么没有发生这种情况(想象一下C预处理器能够调用Guile代码进行宏扩展!)可能是出于社会和经济原因.
您有时仍应考虑使用其他预处理器(如m4或GPP)来生成 C代码.见autoconf.