繁体   English   中英

大C宏。有什么好处?

[英]Large C macros. What's the benefit?

我一直在使用一个主要由不再在公司工作的程序员编写的大型代码库。 其中一位程序员显然在他心中有一个特殊的位置,用于非常长的宏。 我可以看到使用宏的唯一好处是能够编写不需要在所有参数中传递的函数(建议在我阅读的最佳实践指南中使用)。 除此之外,我认为内联函数没有任何好处。

有些宏是如此复杂,我很难想象有人甚至写它们。 我尝试用这种精神创造一个,这是一场噩梦。 调试是非常困难的,因为它在调试器中将N +行代码变为1(例如,在这个大块代码中的某处存在段错误。祝你好运!)。 我必须实际拉出宏并运行它非宏观调试它。 我能看到这个人编写这些内容的唯一方法就是在他调试之后用一个函数编写的代码自动生成它们(或者通过比我更聪明并且第一次完美地编写它,我猜这总是可能的) 。

我错过了什么吗? 我疯了吗? 有没有我不知道的调试技巧? 请填写我。我真的很想听听观众中的宏观爱好者。 :)

对我来说,宏的最佳用途是压缩代码并减少错误。 缺点显然是在调试中,因此必须小心使用它们。

我倾向于认为如果生成的代码不是一个数量级更小并且更不容易出错(意味着宏处理一些簿记细节)那么它就不值得了。

在C ++中,许多这样的用法可以用模板替换,但不是全部。 有用的宏的一个简单示例是在MFC的事件处理程序宏中 - 没有它们,创建事件表将更难以正确完成,而您必须编写(和读取)的代码将更加复杂。

如果宏非常长,它们可能会使代码变短但效率很高。 实际上,他可能已经使用宏来显式内联代码或从运行时代码路径中删除决策点。

重要的是要理解,在过去,许多编译器并没有完成这样的优化,而今天我们认为理所当然的一些事情,比如快速函数调用,则无效。

对我来说,宏是邪恶的。 由于它们有如此多的副作用,而且在C ++中你可以通过内联获得相同的性能增益,因此它们不值得冒险。

对于前者 看到这个短宏:

#define max(a, b) ((a)>(b)?(a):(b))

然后尝试这个电话:

max(i++, j++)

更多。 说你有

#define PLANETS 8
#define SOCCER_MIDDLE_RIGHT 8

如果抛出错误,它将引用'8',但不是它的任何一个有意义的表示。

我只知道做你所描述的两个原因。

首先是强制函数内联。 这几乎毫无意义,因为内联关键字通常做同样的事情,而函数内联通常是过早的微优化。

其次是用C或C ++模拟嵌套函数。 这与你的“编写函数不需要在所有参数中传递”有关,但实际上可能比这更强大。 Walter Bright给出了嵌套函数有用的示例。

使用宏还有其他原因,例如使用预处理器特定的功能(如在自动生成的错误消息中包含__FILE____LINE__ )或以函数和模板不能的方式减少样板代码( Boost.Preprocessor库在这里擅长;参见Boost.ScopeExit示例的示例枚举代码 ),但这些原因似乎并不适用于您所描述的内容。

非常长的宏将具有性能缺陷,例如增加的编译二进制大小,并且肯定有其他原因不使用它们。

对于最有问题的宏,我会考虑通过预处理器运行代码,并用函数调用(如果可能的话内联)或直接LOC替换宏输出。 如果存在与其他体系结构/操作系统兼容的宏,则可能会遇到困难。

部分好处是代码复制没有最终的维护成本 - 也就是说,不是在其他地方复制代码,而是从中创建一个宏,只需要编辑一次......

当然,你也可以只是制作一个方法来调用,但这是更多的工作......我自己反对宏观用法,只是试图提出一个潜在的理由。

在C中编写宏有很多好的理由。

一些最重要的是使用x-macros创建配置表,用于创建类似宏的函数,可以接受多个参数类型作为输入,并将表从人类可读/可配置/可理解的值转换为计算机使用的值。

我不能真正看到人们编写非常长的宏的原因,除了历史自动函数内联。

我会说,在调试复杂的宏时 ,(当编写X宏等时)我倾向于预处理源文件并用预处理的文件替换原始文件。

这允许您查看生成的C代码,并为您提供在调试器中使用的实际行。

我根本不使用宏。 内联函数服务于宏可以执行的每个有用的目的。 宏允许你做一些非常奇怪和违反直觉的事情,比如拆分标识符(那么有人如何搜索标识符?)。

我使用过的一些遗留代码在方法的位置上使用了非常广泛的宏。 原因是计算机/ OS /运行时具有极小的堆栈,因此堆栈溢出是一个常见问题。 使用宏而不是方法意味着堆栈上的方法更少。

幸运的是,大部分代码都已过时,因此现在(大部分)都已消失。

C89没有内联功能。 如果使用禁用扩展的编译器(由于多种原因这是一件令人满意的事情),那么宏可能是唯一的选择。

尽管C99在1999年问世,但长期以来一直存在阻力。 商业编译器供应商并不觉得值得花时间来实现C99。 有些人(例如MS)还没有。 因此,对于许多公司来说,使用C99符合模式并不是一个可行的实际决定,即使在某些编译器的情况下也是如此。

我已经使用了具有内联函数扩展的C89编译器,但是扩展程序是错误的(例如,当不应该存在多个定义错误时),这样的事情可能会阻止程序员使用内联函数。

另一件事是宏版本有效地强制该函数实际上将被内联。 C99 inline关键字只是一个编译器提示,编译器可能仍然决定生成一个像非内联函数链接的函数代码的单个实例。 (如果函数不是微不足道并且返回void ,我仍然使用的一个编译器将执行此操作)。

我还参与了一个产品,一个传统的程序员(幸好早已离开)也与Macros有特别的恋情。 他的“定制”脚本语言是邋。的高度。 他在C语言中编写了C ++类,这意味着所有类函数和变量都是公共的。 无论如何,他几乎用宏观和可变函数写出了所有东西(世界上另一个可怕的怪物)。 所以不是写一个合适的模板类,而是使用宏代替! 他还使用宏来创建工厂类,而不是普通的代码......他的代码几乎是不可思议的。

从我所看到的,宏可以在它们很小时使用,并且以声明方式使用,并且不包含诸如循环之类的移动部分以及其他程序流表达式。 如果宏是一行或最多两行,并且它声明了某事物的实例,那就没关系。 在运行时不会破坏的东西。 宏也不应该包含类定义或函数定义。 如果宏包含需要使用调试器进入的代码,则应删除宏并将其替换为其他内容。

它们还可用于包装自定义跟踪/调试功能。 例如,您希望在调试版本中进行自定义跟踪,而不是发布版本。

无论如何,当你在这样的遗留代码中工作时,一定要一次删除一点宏的混乱。 如果你坚持下去,有足够的时间,你最终将把它们全部删除,让自己的生活更轻松。 我过去做过这个,特别是凌乱的宏。 我所做的是打开编译器开关让预处理器生成一个输出文件。 然后我突袭该文件,复制代码,重新缩进,并用生成的代码替换宏。 谢天谢地,该编译器功能。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM