简体   繁体   English

大C宏。有什么好处?

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

I've been working with a large codebase written primarily by programmers who no longer work at the company. 我一直在使用一个主要由不再在公司工作的程序员编写的大型代码库。 One of the programmers apparently had a special place in his heart for very long macros. 其中一位程序员显然在他心中有一个特殊的位置,用于非常长的宏。 The only benefit I can see to using macros is being able to write functions that don't need to be passed in all their parameters (which is recommended against in a best practices guide I've read). 我可以看到使用宏的唯一好处是能够编写不需要在所有参数中传递的函数(建议在我阅读的最佳实践指南中使用)。 Other than that I see no benefit over an inline function. 除此之外,我认为内联函数没有任何好处。

Some of the macros are so complicated I have a hard time imagining someone even writing them. 有些宏是如此复杂,我很难想象有人甚至写它们。 I tried creating one in that spirit and it was a nightmare. 我尝试用这种精神创造一个,这是一场噩梦。 Debugging is extremely difficult, as it takes N+ lines of code into 1 in the a debugger (eg there was a segfault somewhere in this large block of code. Good luck!). 调试是非常困难的,因为它在调试器中将N +行代码变为1(例如,在这个大块代码中的某处存在段错误。祝你好运!)。 I had to actually pull the macro out and run it un-macro-tized to debug it. 我必须实际拉出宏并运行它非宏观调试它。 The only way I could see the person having written these is by automatically generating them out of code written in a function after he had debugged it (or by being smarter than me and writing it perfectly the first time, which is always possible I guess). 我能看到这个人编写这些内容的唯一方法就是在他调试之后用一个函数编写的代码自动生成它们(或者通过比我更聪明并且第一次完美地编写它,我猜这总是可能的) 。

Am I missing something? 我错过了什么吗? Am I crazy? 我疯了吗? Are there debugging tricks I'm not aware of? 有没有我不知道的调试技巧? Please fill me in. I would really like to hear from the macro-lovers in the audience. 请填写我。我真的很想听听观众中的宏观爱好者。 :) :)

To me the best use of macros is to compress code and reduce errors. 对我来说,宏的最佳用途是压缩代码并减少错误。 The downside is obviously in debugging, so they have to be used with care. 缺点显然是在调试中,因此必须小心使用它们。

I tend to think that if the resulting code isn't an order of magnitude smaller and less prone to errors (meaning the macros take care of some bookkeeping details) then it wasn't worth it. 我倾向于认为如果生成的代码不是一个数量级更小并且更不容易出错(意味着宏处理一些簿记细节)那么它就不值得了。

In C++, many uses like this can be replaced with templates, but not all. 在C ++中,许多这样的用法可以用模板替换,但不是全部。 A simple example of Macros that are useful are in the event handler macros of MFC -- without them, creating event tables would be much harder to get right and the code you'd have to write (and read) would be much more complex. 有用的宏的一个简单示例是在MFC的事件处理程序宏中 - 没有它们,创建事件表将更难以正确完成,而您必须编写(和读取)的代码将更加复杂。

If the macros are extremely long, they probably make the code short but efficient. 如果宏非常长,它们可能会使代码变短但效率很高。 In effect, he might have used macros to explicitly inline code or remove decision points from the run-time code path. 实际上,他可能已经使用宏来显式内联代码或从运行时代码路径中删除决策点。

It might be important to understand that, in the past, such optimizations weren't done by many compilers, and some things that we take for granted today, like fast function calls, weren't valid then. 重要的是要理解,在过去,许多编译器并没有完成这样的优化,而今天我们认为理所当然的一些事情,比如快速函数调用,则无效。

To me, macros are evil. 对我来说,宏是邪恶的。 With their so many side effects, and the fact that in C++ you can gain same perf gains with inline, they are not worth the risk. 由于它们有如此多的副作用,而且在C ++中你可以通过内联获得相同的性能增益,因此它们不值得冒险。

For ex. 对于前者 see this short macro: 看到这个短宏:

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

then try this call: 然后尝试这个电话:

max(i++, j++)

More. 更多。 Say you have 说你有

#define PLANETS 8
#define SOCCER_MIDDLE_RIGHT 8

if an error is thrown, it will refer to '8', but not either of its meaninful representations. 如果抛出错误,它将引用'8',但不是它的任何一个有意义的表示。

I only know of two reasons for doing what you describe. 我只知道做你所描述的两个原因。

First is to force functions to be inlined. 首先是强制函数内联。 This is pretty much pointless, since the inline keyword usually does the same thing, and function inlining is often a premature micro-optimization anyway. 这几乎毫无意义,因为内联关键字通常做同样的事情,而函数内联通常是过早的微优化。

Second is to simulate nested functions in C or C++. 其次是用C或C ++模拟嵌套函数。 This is related to your "writing functions that don't need to be passed in all their parameters" but can actually be quite a bit more powerful than that. 这与你的“编写函数不需要在所有参数中传递”有关,但实际上可能比这更强大。 Walter Bright gives examples of where nested functions can be useful. Walter Bright给出了嵌套函数有用的示例。

There are other reasons to use of macros, such as using preprocessor-specific functionality (like including __FILE__ and __LINE__ in autogenerated error messages) or reducing boilerplate code in ways that functions and templates can't (the Boost.Preprocessor library excels here; see Boost.ScopeExit or this sample enum code for examples), but these reasons don't seem to apply for doing what you describe. 使用宏还有其他原因,例如使用预处理器特定的功能(如在自动生成的错误消息中包含__FILE____LINE__ )或以函数和模板不能的方式减少样板代码( Boost.Preprocessor库在这里擅长;参见Boost.ScopeExit示例的示例枚举代码 ),但这些原因似乎并不适用于您所描述的内容。

Very long macros will have performance drawbacks, like increased compiled binary size, and there are certainly other reasons for not using them. 非常长的宏将具有性能缺陷,例如增加的编译二进制大小,并且肯定有其他原因不使用它们。

For the most problematic macros, I would consider running the code through the preprocessor, and replacing the macro output with function calls (inline if possible) or straight LOC. 对于最有问题的宏,我会考虑通过预处理器运行代码,并用函数调用(如果可能的话内联)或直接LOC替换宏输出。 If the macros exists for compatibility with other architectures/OS's, you might be stuck though. 如果存在与其他体系结构/操作系统兼容的宏,则可能会遇到困难。

Part of the benefit is code replication without the eventual maintenance cost - that is, instead of copying code elsewhere you create a macro from it and only have to edit it once... 部分好处是代码复制没有最终的维护成本 - 也就是说,不是在其他地方复制代码,而是从中创建一个宏,只需要编辑一次......

Of course, you could also just make a method to be called but that is sort of more work... I'm against much macro use myself, just trying to present a potential rationale. 当然,你也可以只是制作一个方法来调用,但这是更多的工作......我自己反对宏观用法,只是试图提出一个潜在的理由。

There are a number of good reasons to write macros in C. 在C中编写宏有很多好的理由。

Some of the most important are for creating configuration tables using x-macros, for making function like macros that can accept multiple parameter types as inputs and converting tables from human readable/configurable/understandable values into computer used values. 一些最重要的是使用x-macros创建配置表,用于创建类似宏的函数,可以接受多个参数类型作为输入,并将表从人类可读/可配置/可理解的值转换为计算机使用的值。

I cant really see a reason for people to write very long macros, except for the historic automatic function inline. 我不能真正看到人们编写非常长的宏的原因,除了历史自动函数内联。

I would say that when debugging complex macros , (when writing X macros etc) I tend to preprocess the source file and substitute the preprocessed file for the original. 我会说,在调试复杂的宏时 ,(当编写X宏等时)我倾向于预处理源文件并用预处理的文件替换原始文件。

This allows you to see the C code generated, and gives you real lines to work with in the debugger. 这允许您查看生成的C代码,并为您提供在调试器中使用的实际行。

I don't use macros at all. 我根本不使用宏。 Inline functions serve every useful purpose a macro can do. 内联函数服务于宏可以执行的每个有用的目的。 Macro allow you to do very weird and counterintuitive things like splitting up identifiers (How does someone search for the identifier then?). 宏允许你做一些非常奇怪和违反直觉的事情,比如拆分标识符(那么有人如何搜索标识符?)。

Some of the legacy code I've worked with used macros very extensively in the place of methods. 我使用过的一些遗留代码在方法的位置上使用了非常广泛的宏。 The reasoning was that the computer/OS/runtime had an extremely small stack, so that stack overflows were a common problem. 原因是计算机/ OS /运行时具有极小的堆栈,因此堆栈溢出是一个常见问题。 Using macros instead of methods meant that there were fewer methods on the stack. 使用宏而不是方法意味着堆栈上的方法更少。

Luckily, most of that code was obsolete, so it is (mostly) gone now. 幸运的是,大部分代码都已过时,因此现在(大部分)都已消失。

C89 did not have inline functions. C89没有内联功能。 If using a compiler with extensions disabled (which is a desirable thing to do for several reasons), then the macro might be the only option. 如果使用禁用扩展的编译器(由于多种原因这是一件令人满意的事情),那么宏可能是唯一的选择。

Although C99 came out in 1999, there was resistance to it for a long time; 尽管C99在1999年问世,但长期以来一直存在阻力。 commercial compiler vendors didn't feel it was worth their time to implement C99. 商业编译器供应商并不觉得值得花时间来实现C99。 Some (eg MS) still haven't. 有些人(例如MS)还没有。 So for many companies it was not a viable practical decision to use C99 conforming mode, even up to today in the case of some compilers. 因此,对于许多公司来说,使用C99符合模式并不是一个可行的实际决定,即使在某些编译器的情况下也是如此。

I have used C89 compilers that did have an extension for inline functions, but the extension was buggy (eg multiple definition errors when there should not be), things like that may dissuade a programmer from using inline functions. 我已经使用了具有内联函数扩展的C89编译器,但是扩展程序是错误的(例如,当不应该存在多个定义错误时),这样的事情可能会阻止程序员使用内联函数。

Another thing is that the macro version effectively forces that the function will actually be inlined. 另一件事是宏版本有效地强制该函数实际上将被内联。 The C99 inline keyword is only a compiler hint and the compiler may still decide to generate a single instance of the function code which is linked like a non-inline function. C99 inline关键字只是一个编译器提示,编译器可能仍然决定生成一个像非内联函数链接的函数代码的单个实例。 (One compiler that I still use will do this if the function is not trivial and returning void ). (如果函数不是微不足道并且返回void ,我仍然使用的一个编译器将执行此操作)。

I have also worked on a product where a legacy programmer (who thankfully is long gone) also had a special love affair with Macros. 我还参与了一个产品,一个传统的程序员(幸好早已离开)也与Macros有特别的恋情。 His 'custom' scripting language is the height of sloppiness. 他的“定制”脚本语言是邋。的高度。 This was compounded by the fact that he wrote his C++ classes in C, meaning all class functions and variables were all public. 他在C语言中编写了C ++类,这意味着所有类函数和变量都是公共的。 Anyways, he wrote almost everything in macro's and variadic functions (Another hideous monstrosity foisted on the world). 无论如何,他几乎用宏观和可变函数写出了所有东西(世界上另一个可怕的怪物)。 So instead of writing a proper template class he would use a Macro instead! 所以不是写一个合适的模板类,而是使用宏代替! He also resorted to macro's to create factory classes as well, instead of normal code... His code is pretty much unmaintanable. 他还使用宏来创建工厂类,而不是普通的代码......他的代码几乎是不可思议的。

From what I have seen, macro's can be used when they are small and are used declaratively and don't contain moving parts like loops, and other program flow expressions. 从我所看到的,宏可以在它们很小时使用,并且以声明方式使用,并且不包含诸如循环之类的移动部分以及其他程序流表达式。 It's OK if the macro is one or at the most two lines long and it declares and instance of something. 如果宏是一行或最多两行,并且它声明了某事物的实例,那就没关系。 Something that won't break during runtime. 在运行时不会破坏的东西。 Also macro's should not contain class definitions, or function definitions. 宏也不应该包含类定义或函数定义。 If the macro contains code that needs to be stepped into using a debugger than the macro should be removed and replace with something else. 如果宏包含需要使用调试器进入的代码,则应删除宏并将其替换为其他内容。

They can also be useful for wrapping custom tracing/debugging functionality. 它们还可用于包装自定义跟踪/调试功能。 For instance you want custom tracing in debug builds but not release builds. 例如,您希望在调试版本中进行自定义跟踪,而不是发布版本。

Anyways when you are working in legacy code like that, just be sure to remove a bit of the macro mess a bit at a time. 无论如何,当你在这样的遗留代码中工作时,一定要一次删除一点宏的混乱。 If you keep it up, with enough time eventually you will remove them all and make life a bit easier for yourself. 如果你坚持下去,有足够的时间,你最终将把它们全部删除,让自己的生活更轻松。 I have done this in the past, with especially messy macro's. 我过去做过这个,特别是凌乱的宏。 What I do is turn on the compiler switch to have the preprocessor generate an output file. 我所做的是打开编译器开关让预处理器生成一个输出文件。 Then I raid that file, and copy the code, re-indent it, and replace the macro with the generated code. 然后我突袭该文件,复制代码,重新缩进,并用生成的代码替换宏。 Thank goodness for that compiler feature. 谢天谢地,该编译器功能。

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

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