当我深入研究 C 编程语言时,我很难理解为什么宏应该作为最后的手段使用。例如,this post .这不是我第一次听到喋喋不休说他们是最后的手段。有人认为内存占用比调用函数 (this post) 还要多。尽管我理解这些论点,以及为什么它们不应该用于 C++(编译器优化等),但我不理解以下内容:
由于宏在堆栈的 .text 段中“展开”(如果您愿意的话),与函数调用相比,与宏相关的开销更少 - 例如不需要在帧指针和堆栈指针之间分配内存。这种内存开销是可以量化的,其中 this post建议宏不是。
我从事的大部分工作都是嵌入式系统、微 Controller 和系统编程。我读过很多 Bjarne Stroustrup 的书和文章。我也在读Clean Code - Robert Martin 坚持认为可读性为王(并不意味着宏本身可以提高可读性)。
TLDR:
考虑到宏可以减少与堆栈帧相关的开销,并且(如果使用得当)可以提高可读性——为什么会有负面的污名?它们散落在 BSD 论文和手册页中。
有些人会投票结束,因为这可以被解释为一个意见问题,但有一些事实。
- 如果您有一个不错的编译器,对简单函数的调用会像宏一样展开内联,但它们的类型安全性要高得多。我见过内联函数更快的情况,因为编译器错过了编译宏中的公共(public)子表达式,这些子表达式在函数参数被内联时被消除。
- 当您多次调用一个简单函数时,您可以提示编译器是每次都内联扩展还是调用它——用代码空间换取您提到的微小堆栈和运行时开销。更好的是,许多编译器(如
gcc
)会根据可能比您的直觉更好的启发式方法自动进行此调用。使用宏,您只能修改宏来调用函数。代码更改比构建提示更容易出错。
- 在大多数编译系统中,错误消息和调试器不引用宏的主体代码。 OTOH,现代调试器将通过函数主体逐行正确地执行,即使它实际上是内联扩展的。同样,编译器将正确指向函数体中的错误位置。
- 通过扩展多个 arg 引用来编码细微错误非常容易:
#define MAX(X,Y) (X>Y?X:Y)
后跟 MAX(++x , 3)
。如果 x
小于 3,这里递增一次,否则递增两次。哎哟。
- 多级宏扩展(一个宏调用产生其他宏调用)依赖于复杂的规则,因此容易出错。例如。不难创建依赖于一个预处理器特定行为的宏,以便它们在另一个预处理器上失败。
- 函数可以递归。宏不能。
- C11 功能提供了一些方法来处理过去(滥用)使用过的宏:例如聚合文字。同样,优点是类型安全、错误和调试器引用。
结果是,当您使用宏时,您会增加内联函数所不存在的错误风险和维护成本。很难逃避这样的结论,即您应该仅将宏用作后备。不这样做的主要正当理由是您被迫使用旧的或垃圾的编译器。