我写了一点C语言,我可以很好地阅读它,大致了解它在做什么,但每次我遇到宏时,它都会让我彻底崩溃。 我最终不得不记住宏是什么,并在阅读时将其替换在我的脑海中。 我遇到的那些直观且易于理解的总是像小迷你函数,所以我总是想知道为什么它们不仅仅是函数。
我可以理解需要在预处理器中为调试或跨平台构建定义不同的构建类型,但定义任意替换的能力似乎只会使本来就很困难的语言变得更加难以理解。
为什么要为 C 语言引入如此复杂的预处理器? 有没有人有一个使用它的例子,让我理解为什么它似乎仍然被用于简单的 if #debug 风格条件编译以外的目的?
编辑:
读了很多答案后我仍然不明白。 最常见的答案是内联代码。 如果 inline 关键字不这样做,那么要么它有充分的理由不这样做,要么实现需要修复。 我不明白为什么需要一种完全不同的机制,这意味着“真正内联此代码”(除了内联之前编写的代码之外)。 我也不明白提到的“如果它太愚蠢而不能放入函数中”的想法。 当然,任何接受输入并产生输出的代码最好放在函数中。 我想我可能不明白,因为我不习惯编写 C 语言的微观优化,但预处理器感觉就像是一些简单问题的复杂解决方案。
I have written a little bit of C, and I can read it well enough to get a general idea of what it is doing, but every time I have encountered a macro it has thrown me completely. I end up having to remember what the macro is and substitute it in my head as I read. The ones that I have encountered that were intuitive and easy to understand were always like little mini functions, so I always wondered why they weren't just functions.
I can understand the need to define different build types for debug or cross platform builds in the preprocessor but the ability to define arbitrary substitutions seems to be useful only to make an already difficult language even more difficult to understand.
Why was such a complex preprocessor introduced for C? And does anyone have an example of using it that will make me understand why it still seems to be used for purposes other than simple if #debug style conditional compilations?
Edit:
Having read a number of answers I still just don't get it. The most common answer is to inline code. If the inline keyword doesn't do it then either it has a good reason to not do it, or the implementation needs fixing. I don't understand why a whole different mechanism is needed that means "really inline this code" (aside form the code being written before inline was around). I also don't understand the idea that was mentioned that "if its too silly to be put in a function". Surely any piece of code that takes an input and produces an output is best put in a function. I think I may not be getting it because I am not used to the micro optimisations of writing C, but the preprocessor just feels like a complex solution to a few simple problems.
发布评论
评论(18)
鉴于您问题中的评论,您可能没有完全意识到调用函数可能会带来相当大的开销。 参数和关键寄存器可能必须在传入时复制到堆栈,并在传出时展开堆栈。 对于较旧的英特尔芯片尤其如此。 宏让程序员(几乎)保留函数的抽象,但避免了函数调用的昂贵开销。 inline 关键字是建议性的,但编译器可能并不总是正确的。 “C”的荣耀和危险在于您通常可以按照您的意愿改变编译器。
在你的面包和黄油中,日常应用程序编程这种微优化(避免函数调用)通常比无用更糟糕,但如果你正在编写一个由操作系统内核调用的时间关键函数,那么它可以产生巨大的影响。
Given the comments in your question, you may not fully appreciate is that calling a function can entail a fair amount of overhead. The parameters and key registers may have to be copied to the stack on the way in, and the stack unwound on the way out. This was particularly true of the older Intel chips. Macros let the programmer keep the abstraction of a function (almost), but avoided the costly overhead of a function call. The inline keyword is advisory, but the compiler may not always get it right. The glory and peril of 'C' is that you can usually bend the compiler to your will.
In your bread and butter, day-to-day application programming this kind of micro-optimization (avoiding function calls) is generally worse then useless, but if you are writing a time-critical function called by the kernel of an operating system, then it can make a huge difference.
与常规函数不同,您可以在宏中控制流程(if、while、for...)。 这是一个例子:
Unlike regular functions, you can do control flow (if, while, for,...) in macros. Here's an example:
它有利于内联代码并避免函数调用开销。 如果您想稍后更改行为而不编辑很多地方,也可以使用它。 它对于复杂的事情没有用处,但是对于您想要内联的简单代码行来说,它还不错。
It's good for inlining code and avoiding function call overhead. As well as using it if you want to change the behaviour later without editing lots of places. It's not useful for complex things, but for simple lines of code that you want to inline, it's not bad.
通过利用 C 预处理器的文本操作,我们可以构造与 C 语言等效的多态数据结构。 使用这种技术,我们可以构建一个可靠的原始数据结构工具箱,可以在任何 C 程序中使用,因为它们利用 C 语法而不是任何特定实现的细节。
这里给出了如何使用宏来管理数据结构的详细说明 - http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html
By leveraging C preprocessor's text manipulation one can construct the C equivalent of a polymorphic data structure. Using this technique we can construct a reliable toolbox of primitive data structures that can be used in any C program, since they take advantage of C syntax and not the specifics of any particular implementation.
Detailed explanation on how to use macros for managing data structure is given here - http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html
宏可以让你摆脱复制粘贴的片段,这是你无法通过任何其他方式消除的。
例如(真实代码,VS 2010编译器的语法):
这是你传递的地方将同名的字段值放入脚本引擎中。 这是复制粘贴的吗? 是的。
DisplayName
用作脚本的字符串和编译器的字段名称。 那不好吗? 是的。 如果您重构代码并将LocalName
重命名为RelativeFolderName
(就像我所做的那样),并且忘记对字符串执行相同的操作(就像我所做的那样),则脚本将在以您意想不到的方式(事实上,在我的示例中,这取决于您是否忘记在单独的脚本文件中重命名该字段,但如果该脚本用于序列化,则这将是 100% 的错误)。如果您为此使用宏,则该错误将没有空间:
不幸的是,这为其他类型的错误打开了大门。 您可以在编写宏时犯一个拼写错误,但永远不会看到损坏的代码,因为编译器不会显示所有预处理后的样子。 其他人可以使用相同的名称(这就是为什么我使用
#undef
尽快“发布”宏)。 所以,明智地使用它。 如果您看到另一种摆脱复制粘贴代码(例如函数)的方法,请使用这种方法。 如果您发现使用宏删除复制粘贴的代码并不值得,请保留复制粘贴的代码。Macros let you get rid of copy-pasted fragments, which you can't eliminate in any other way.
For instance (the real code, syntax of VS 2010 compiler):
This is the place where you pass a field value under the same name into a script engine. Is this copy-pasted? Yes.
DisplayName
is used as a string for a script and as a field name for the compiler. Is that bad? Yes. If you refactor you code and renameLocalName
toRelativeFolderName
(as I did) and forget to do the same with the string (as I did), the script will work in a way you don't expect (in fact, in my example it depends on did you forget to rename the field in a separate script file, but if the script is used for serialization, it would be a 100% bug).If you use a macro for this, there will be no room for the bug:
Unfortunately, this opens a door for other types of bugs. You can make a typo writing the macro and will never see a spoiled code, because the compiler doesn't show how it looks after all preprocessing. Someone else could use the same name (that's why I "release" macros ASAP with
#undef
). So, use it wisely. If you see another way of getting rid of copy-pasted code (such as functions), use that way. If you see that getting rid of copy-pasted code with macros isn't worth the result, keep the copy-pasted code.明显的原因之一是,通过使用宏,代码将在编译时扩展,并且您可以获得伪函数调用,而无需调用开销。
否则,您还可以将它用于符号常量,这样您就不必在多个位置编辑相同的值来更改一件小事情。
One of the obvious reasons is that by using a macro, the code will be expanded at compile time, and you get a pseudo function-call without the call overhead.
Otherwise, you can also use it for symbolic constants, so that you don't have to edit the same value in several places to change one small thing.
宏 .. 当您的 &#(*$& 编译器拒绝内联某些内容时。
这应该是一个励志海报,不是吗?
严肃地说,谷歌 预处理器滥用(您可能会看到与 #1 结果类似的 SO 问题)。其他
人会反对使用 #if 进行条件编译.. 他们宁愿你:
而不是
.. 出于调试目的,因为你可以在调试器中看到 if() 但看不到 #if 然后我们深入了解 #ifdef 与。 #if。
如果代码少于 10 行,请尝试内联它。如果无法内联,请尝试优化它。如果它太愚蠢,无法成为函数,请创建一个宏。
Macros .. for when your &#(*$& compiler just refuses to inline something.
That should be a motivational poster, no?
In all seriousness, google preprocessor abuse (you may see a similar SO question as the #1 result). If I'm writing a macro that goes beyond the functionality of assert(), I usually try to see if my compiler would actually inline a similar function.
Others will argue against using #if for conditional compilation .. they would rather you:
rather than
.. for debugging purposes, since you can see the if() but not #if in a debugger. Then we dive into #ifdef vs #if.
If its under 10 lines of code, try to inline it. If it can't be inlined, try to optimize it. If its too silly to be a function, make a macro.
虽然我不太喜欢宏,也不再倾向于编写太多 C 语言,但根据我当前的任务,像这样的东西(显然可能会产生一些副作用)很方便:
现在我还没有写任何东西多年来一直如此,但这样的“功能”遍布我职业生涯早期维护的代码中。 我想扩展也算方便吧。
While I'm not a big fan of macros and don't tend to write much C anymore, based on my current tasking, something like this (which could obviously have some side-effects) is convenient:
Now I haven't written anything like that in years, but 'functions' like that were all over code that I maintained earlier in my career. I guess the expansion could be considered convenient.
我没有看到有人提到这一点,关于宏等函数,例如:
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
一般来说,建议在不必要时避免使用宏,原因有很多,可读性是主要考虑的问题。 所以:
几乎从来没有,因为有一个更具可读性的替代方案,即
inline
,请参阅 https://www.greenend.org.uk/rjk/tech/inline.html或 http://www.cplusplus.com/articles/2LywvCM9/ (第二个链接是一个 C++ 页面,但据我所知,这一点适用于 c 编译器)。
现在,细微的区别是宏由预处理器处理,内联由编译器处理,但现在没有实际区别。
适用于小型功能(最多两个或三个衬垫)。 目标是在程序运行时获得一些优势,因为像宏(和内联函数)这样的函数是在预处理(或内联情况下的编译)期间完成的代码替换,而不是存在于内存中的真实函数,因此没有函数调用开销(链接页面中有更多详细信息)。
I didn't see anyone mentioning this so, regarding function like macros, eg:
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
Generally it's recommended to avoid using macros when not necessary, for many reasons, readability being the main concern. So:
Almost never, since there's a more readable alternative which is
inline
, see https://www.greenend.org.uk/rjk/tech/inline.htmlor http://www.cplusplus.com/articles/2LywvCM9/ (the second link is a C++ page, but the point is applicable to c compilers as far as I know).
Now, the slight difference is that macros are handled by the pre-processor and inline is handled by the compiler, but there's no practical difference nowadays.
For small functions (two or three liners max). The goal is to gain some advantage during the run time of a program, as function like macros (and inline functions) are code replacements done during the pre-proccessing (or compilation in case of inline) and are not real functions living in memory, so there's no function call overhead (more details in the linked pages).
这似乎对宏的命名反映不佳。 我假设如果它是
log_function_entry()
宏,您就不必模拟预处理器。通常它们应该是这样,除非它们需要对通用参数进行操作。
将适用于带有
<
运算符的任何类型。宏不仅仅是函数,还允许您使用源文件中的符号执行操作。 这意味着您可以创建新的变量名称,或引用宏所在的源文件和行号。
在 C99 中,宏还允许您调用可变参数函数,例如
printf
,其中格式的工作方式类似于
printf
。 如果守卫为真,它将输出消息以及打印该消息的文件和行号。 如果它是一个函数调用,它不会知道您调用它的文件和行,并且使用vaprintf
会做更多的工作。That seems to reflect poorly on the naming of the macros. I would assume you wouldn't have to emulate the preprocessor if it were a
log_function_entry()
macro.Usually they should be, unless they need to operate on generic parameters.
will work on any type with an
<
operator.More that just functions, macros let you perform operations using the symbols in the source file. That means you can create a new variable name, or reference the source file and line number the macro is on.
In C99, macros also allow you to call variadic functions such as
printf
In which the format works like
printf
. If the guard is true, it outputs the message along with the file and line number that printed the message. If it was a function call, it would not know the file and line you called it from, and using avaprintf
would be a bit more work.通过比较
C
宏的几种使用方式以及如何在D
中实现它们,这段摘录基本上总结了我对此事的看法。复制自 DigitalMars.com
宏
预处理器宏为
C
添加了强大的功能和灵活性。 但它们有一个缺点:#include
包含数万行宏定义时,避免无意的宏扩展就会出现问题。C++
中的模板引入的。)下面列举了宏的常见用途以及 D 中的相应功能:
定义文字常量:
C
预处理器方式<前><代码>#define VALUE 5
D
方式创建值或标志列表:
C
预处理器方式D
方式设置函数调用约定:
C
预处理器方式<前><代码>#ifndef _CRTAPI1
#define _CRTAPI1 __cdecl
#万一
#ifndef_CRTAPI2
#define _CRTAPI2 __cdecl
#万一
int _CRTAPI2 func();
D
方式可以在块中指定调用约定,因此无需为每个函数更改它:
简单的泛型编程:
C
预处理器方式根据文本替换选择要使用的函数:
<前><代码>#ifdef UNICODE
int getValueW(wchar_t *p);
#define getValue getValueW
#别的
int getValueA(char *p);
#define getValue getValueA
#万一
D
方式D
允许将符号声明为其他符号的别名:DigitalMars 网站。
This excerpt pretty much sums up my view on the matter, by comparing several ways that
C
macros are used, and how to implement them inD
.copied from DigitalMars.com
Macros
Preprocessor macros add powerful features and flexibility to
C
. But they have a downside:#include
'ing tens of thousands of lines of macro definitions, it becomes problematical to avoid inadvertent macro expansions.C++
.)Here's an enumeration of the common uses for macros, and the corresponding feature in D:
Defining literal constants:
The
C
Preprocessor WayThe
D
WayCreating a list of values or flags:
The
C
Preprocessor WayThe
D
WaySetting function calling conventions:
The
C
Preprocessor WayThe
D
WayCalling conventions can be specified in blocks, so there's no need to change it for every function:
Simple generic programming:
The
C
Preprocessor WaySelecting which function to use based on text substitution:
The
D
WayD
enables declarations of symbols that are aliases of other symbols:There are more examples on the DigitalMars website.
它们是一种基于 C 的编程语言(更简单的语言),因此它们对于在编译时进行元编程非常有用……换句话说,您可以编写宏代码,以更少的行数和时间生成 C 代码直接用 C 语言编写。
它们对于编写“多态”或“重载”的“类函数”表达式也非常有用; 例如, max 宏定义为:
对于任何数字类型都很有用; 在 C 中你不能写:
即使你想,因为你不能重载函数。
更不用说条件编译和文件包含(这也是宏语言的一部分)......
They are a programming language (a simpler one) on top of C, so they are useful for doing metaprogramming in compile time... in other words, you can write macro code that generates C code in less lines and time that it will take writing it directly in C.
They are also very useful to write "function like" expressions that are "polymorphic" or "overloaded"; e.g. a max macro defined as:
is useful for any numeric type; and in C you could not write:
even if you wanted, because you cannot overload functions.
And not to mention conditional compiling and file including (that are also part of the macro language)...
宏允许某人在编译期间修改程序行为。 考虑一下:
时意味着未使用的代码甚至不会进入二进制文件,并且构建过程可以修改值,只要它与宏预处理器集成即可。 示例: make ARCH=arm (假设转发宏定义为 cc -DARCH=arm)
简单示例:
(来自 glibc limit.h,定义 long 的最大值)
如果我们正在编译 32 位或 64 位,则在编译时验证(使用 #define __WORDSIZE)。 对于 multilib 工具链,使用参数 -m32 和 -m64 可能会自动更改位大小。
(POSIX 版本请求)
编译时请求 POSIX 2008 支持。 标准库可能支持许多(不兼容)标准,但通过此定义,它将提供正确的函数原型(例如:getline()、no gets() 等)。 例如,如果库不支持该标准,它可能会在编译时给出#error,而不是在执行期间崩溃。
(硬编码路径)
在编译期间定义硬编码目录。 例如,可以使用 -DLIBRARY_PATH=/home/user/lib 进行更改。 如果那是 const char *,您将如何在编译期间配置它?
(pthread.h,编译时的复杂定义)
可能会声明大段文本,否则不会被简化(始终在编译时)。 使用函数或常量(在编译时)不可能做到这一点。
为了避免让事情真正复杂化并避免提出糟糕的编码风格,我不会给出在不同的、不兼容的操作系统中编译的代码示例。 为此,请使用交叉构建系统,但应该清楚的是,预处理器允许在没有构建系统帮助的情况下实现这一点,并且不会因为缺少接口而中断编译。
最后,考虑一下条件编译在嵌入式系统上的重要性,在嵌入式系统中,处理器速度和内存有限,而且系统非常异构。
现在,如果您问,是否可以用正确的定义替换所有宏常量定义和函数调用? 答案是肯定的,但它不会简单地消除在编译期间更改程序行为的需要。 仍然需要预处理器。
Macros allow someone to modify the program behavior during compilation time. Consider this:
At compilation time means that unused code won't even go into the binary and that the build process can modify the values, as long as it's integrated with the macro preprocessor. Example: make ARCH=arm (assumes forwarding macro definition as cc -DARCH=arm)
Simple examples:
(from glibc limits.h, define the largest value of long)
Verifies (using the #define __WORDSIZE) at compile time if we're compiling for 32 or 64 bits. With a multilib toolchain, using parameters -m32 and -m64 may automatically change bit size.
(POSIX version request)
Requests during compilation time POSIX 2008 support. The standard library may support many (incompatible) standards but with this definition, it will provide the correct function prototypes (example: getline(), no gets(), etc.). If the library doesn't support the standard it may give an #error during compile time, instead of crashing during execution, for example.
(hardcoded path)
Defines, during compilation time a hardcode directory. Could be changed with -DLIBRARY_PATH=/home/user/lib, for example. If that were a const char *, how would you configure it during compilation ?
(pthread.h, complex definitions at compile time)
Large pieces of text may that otherwise wouldn't be simplified may be declared (always at compile time). It's not possible to do this with functions or constants (at compile time).
To avoid really complicating things and to avoid suggesting poor coding styles, I'm wont give an example of code that compiles in different, incompatible, operating systems. Use your cross build system for that, but it should be clear that the preprocessor allows that without help from the build system, without breaking compilation because of absent interfaces.
Finally, think about the importance of conditional compilation on embedded systems, where processor speed and memory are limited and systems are very heterogeneous.
Now, if you ask, is it possible to replace all macro constant definitions and function calls with proper definitions ? The answer is yes, but it won't simply make the need for changing program behavior during compilation go away. The preprocessor would still be required.
请记住,宏(和预处理器)来自 C 的早期。它们曾经是执行内联“函数”的唯一方法(因为,当然,inline 是一个非常新的关键字),并且它们仍然是强制内联某些内容的唯一方法。
此外,宏是您可以执行诸如在编译时将文件和行插入字符串常量等技巧的唯一方法。
如今,宏曾经是唯一方法的许多事情都可以通过新的机制更好地处理。 但他们仍然时不时地占有一席之地。
Remember that macros (and the pre-processor) come from the earliest days of C. They used to be the ONLY way to do inline 'functions' (because, of course, inline is a very recent keyword), and they are still the only way to FORCE something to be inlined.
Also, macros are the only way you can do such tricks as inserting the file and line into string constants at compile time.
These days, many of the things that macros used to be the only way to do are better handled through newer mechanisms. But they still have their place, from time to time.
除了内联以提高效率和条件编译之外,宏还可用于提高低级 C 代码的抽象级别。 C 并没有真正让您脱离内存和资源管理的具体细节以及数据的精确布局,并且支持非常有限的信息隐藏形式和用于管理大型系统的其他机制。 使用宏,您不再局限于仅使用 C 语言中的基本构造:您可以定义自己的数据结构和编码构造(包括类和模板!),同时仍然名义上编写 C!
预处理器宏实际上提供了一种在编译时执行的图灵完备语言。 令人印象深刻(但有点可怕)的例子之一是在 C++ 方面: Boost 预处理器库使用 C99/ C++98 预处理器,用于构建(相对)安全的编程结构,然后将其扩展到任何底层声明以及您输入的代码,无论是 C 还是 C++。
在实践中,当您没有自由度以更安全的语言使用高级构造时,我建议将预处理器编程作为最后的手段。 但有时候,如果你的背靠墙而黄鼠狼正在逼近,知道你能做什么是件好事……!
Apart from inlining for efficiency and conditional compilation, macros can be used to raise the abstraction level of low-level C code. C doesn't really insulate you from the nitty-gritty details of memory and resource management and exact layout of data, and supports very limited forms of information hiding and other mechanisms for managing large systems. With macros, you are no longer limited to using only the base constructs in the C language: you can define your own data structures and coding constructs (including classes and templates!) while still nominally writing C!
Preprocessor macros actually offer a Turing-complete language executed at compile time. One of the impressive (and slightly scary) examples of this is over on the C++ side: the Boost Preprocessor library uses the C99/C++98 preprocessor to build (relatively) safe programming constructs which are then expanded to whatever underlying declarations and code you input, whether C or C++.
In practice, I'd recommend regarding preprocessor programming as a last resort, when you don't have the latitude to use high level constructs in safer languages. But sometimes it's good to know what you can do if your back is against the wall and the weasels are closing in...!
来自计算机愚蠢:
这只是宏的一种可能用途。
From Computer Stupidities:
That's just one possible use of Macros.
我将补充已经说过的内容。
因为宏适用于文本替换,所以它们允许您执行使用函数无法执行的非常有用的操作。
以下是宏真正有用的一些情况:
这是一个非常流行且经常使用的宏。 例如,当您需要迭代数组时,这非常方便。
在这里,如果另一个程序员在减法中向
a
添加五个元素并不重要。for
循环将始终遍历所有元素。C 库的比较内存和字符串的函数使用起来相当难看。
您编写:
或
检查
str
是否指向"Hello, world"
。 我个人认为这两个解决方案看起来都相当丑陋且令人困惑(尤其是!strcmp(...)
)。这是一些人(包括我)需要使用
strcmp
/memcmp
比较字符串或内存时使用的两个简洁的宏:现在您可以编写如下代码:
这里意图是不是清晰多了!
这些是宏用于函数无法完成的事情的情况。 宏不应该用来替换函数,但它们还有其他很好的用途。
I will add to whats already been said.
Because macros work on text substitutions they allow you do very useful things which wouldn't be possible to do using functions.
Here a few cases where macros can be really useful:
This is a very popular and frequently used macro. This is very handy when you for example need to iterate through an array.
Here it doesn't matter if another programmer adds five more elements to
a
in the decleration. Thefor
-loop will always iterate through all elements.The C library's functions to compare memory and strings are quite ugly to use.
You write:
or
To check if
str
points to"Hello, world"
. I personally think that both these solutions look quite ugly and confusing (especially!strcmp(...)
).Here are two neat macros some people (including I) use when they need to compare strings or memory using
strcmp
/memcmp
:Now you can now write the code like this:
Here is the intention alot clearer!
These are cases were macros are used for things functions cannot accomplish. Macros should not be used to replace functions but they have other good uses.
宏真正发挥作用的情况之一是用它们进行代码生成。
我曾经在一个旧的 C++ 系统上工作,该系统使用插件系统,以自己的方式将参数传递给插件(使用自定义的类似映射的结构)。 使用一些简单的宏来处理这个怪癖,并允许我们在插件中使用具有正常参数的真正的 C++ 类和函数,而不会出现太多问题。 所有粘合代码均由宏生成。
One of the case where macros really shine is when doing code-generation with them.
I used to work on an old C++ system that was using a plugin system with his own way to pass parameters to the plugin (Using a custom map-like structure). Some simple macros were used to be able to deal with this quirk and allowed us to use real C++ classes and functions with normal parameters in the plugins without too much problems. All the glue code being generated by macros.