c - C 宏的负面污名

标签 c macros

<分区>

当我深入研究 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 功能提供了一些方法来处理过去(滥用)使用过的宏:例如聚合文字。同样,优点是类型安全、错误和调试器引用。

结果是,当您使用宏时,您会增加内联函数所不存在的错误风险和维护成本。很难逃避这样的结论,即您应该仅将宏用作后备。不这样做的主要正当理由是您被迫使用旧的或垃圾的编译器。

关于c - C 宏的负面污名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41315993/

相关文章:

c - 为什么这个查找字符串中第一个唯一字符的 C 程序太慢了?

c++ - usleep 与 clock_gettime() 问题

c++ - GCC 不将 __VA_ARGS__ 视为宏中的参数

objective-c - 如何限制使用的预处理定义的范围?

c++ - 如何在二进制文件中设置内置版本号?

c - 使用数组打印菜单图标不起作用?

c - 使用指针对二维数组中一行中的元素进行排序

c - 'CalAverage' 函数中 if 语句的问题

.net - 您可以使用 EnvDTE 作为预构建事件执行 RunCustomTool 吗?

c++ - C++ 中的 C++ 模板