简体   繁体   English

C 宏有什么用?

[英]What are C macros useful for?

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.我已经写了一点 C,我可以很好地阅读它以大致了解它在做什么,但是每次我遇到一个宏时,它都让我彻底崩溃。 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?为什么要为 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?有没有人有一个使用它的例子,这会让我理解为什么它似乎仍然用于#debug 样式条件编译以外的其他目的?

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.如果 inline 关键字不这样做,那么要么它有充分的理由不这样做,要么实现需要修复。 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.当然,任何接受输入并产生 output 的代码都最好放在 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.我想我可能没有得到它,因为我不习惯编写 C 的微优化,但预处理器感觉就像是一些简单问题的复杂解决方案。

I end up having to remember what the macro is and substitute it in my head as I read.我最终不得不记住宏是什么,并在阅读时将其替换在脑海中。

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.如果它是log_function_entry()宏,我会假设您不必模拟预处理器。

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.我遇到的那些直观易懂的总是像小迷你函数,所以我一直想知道为什么它们不只是函数。

Usually they should be, unless they need to operate on generic parameters.通常它们应该是,除非它们需要对泛型参数进行操作。

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

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在 C99 中,宏还允许您调用可变参数函数,例如printf

#define log_message(guard,format,...) \
   if (guard) printf("%s:%d: " format "\n", __FILE__, __LINE__,__VA_ARGS_);

log_message( foo == 7, "x %d", x)

In which the format works like printf .其中格式类似于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 a vaprintf would be a bit more work.如果它是 function 调用,它不会知道您从中调用它的文件和行,并且使用vaprintf会更有效。

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 in D .通过比较使用C宏的几种方式,以及如何在D中实现它们,这段摘录几乎总结了我对此事的看法。

copied from DigitalMars.com从 DigitalMars.com 复制

Back when C was invented, compiler technology was primitive.早在发明C时,编译器技术还很原始。 Installing a text macro preprocessor onto the front end was a straightforward and easy way to add many powerful features.在前端安装文本宏预处理器是添加许多强大功能的简单直接的方法。 The increasing size & complexity of programs have illustrated that these features come with many inherent problems.程序的规模和复杂性不断增加,这表明这些特性伴随着许多固有的问题。 D doesn't have a preprocessor; D没有预处理器; but D provides a more scalable means to solve the same problems.但是D提供了一种更具可扩展性的方法来解决相同的问题。

Macros

Preprocessor macros add powerful features and flexibility to C .预处理器宏为C添加了强大的功能和灵活性。 But they have a downside:但它们有一个缺点:

  • Macros have no concept of scope;宏没有scope的概念; they are valid from the point of definition to the end of the source.它们从定义点到源代码结束都是有效的。 They cut a swath across.h files, nested code, etc. When #include 'ing tens of thousands of lines of macro definitions, it becomes problematical to avoid inadvertent macro expansions.他们在 .h 文件、嵌套代码等方面进行了大量的处理。当#include执行数万行宏定义时,避免无意中的宏扩展成为问题。
  • Macros are unknown to the debugger.调试器不知道宏。 Trying to debug a program with symbolic data is undermined by the debugger only knowing about macro expansions, not the macros themselves.尝试使用符号数据调试程序会被调试器破坏,只知道宏扩展,而不知道宏本身。
  • Macros make it impossible to tokenize source code, as an earlier macro change can arbitrarily redo tokens.宏使得无法对源代码进行标记,因为早期的宏更改可以任意重做标记。
  • The purely textual basis of macros leads to arbitrary and inconsistent usage, making code using macros error prone.宏的纯文本基础导致任意和不一致的使用,使得使用宏的代码容易出错。 (Some attempt to resolve this was introduced with templates in C++ .) (通过C++中的模板引入了一些解决此问题的尝试。)
  • Macros are still used to make up for deficits in the language's expressive capability, such as for "wrappers" around header files.宏仍然用于弥补语言表达能力的不足,例如 header 文件周围的“包装器”。

Here's an enumeration of the common uses for macros, and the corresponding feature in D:下面列举了宏的常见用途,以及 D 中的相应功能:

  1. Defining literal constants:定义文字常量:

    • The C Preprocessor Way C预处理器方式

      #define VALUE 5
    • The D Way D方式

      const int VALUE = 5;
  2. Creating a list of values or flags:创建值或标志列表:

    • The C Preprocessor Way C预处理器方式

      int flags: #define FLAG_X 0x1 #define FLAG_Y 0x2 #define FLAG_Z 0x4... flags |= FLAG_X;
    • The D Way D方式

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 }; FLAGS flags; ... flags |= FLAGS.X;
  3. Setting function calling conventions:设置 function 调用约定:

    • The C Preprocessor Way C预处理器方式

      #ifndef _CRTAPI1 #define _CRTAPI1 __cdecl #endif #ifndef _CRTAPI2 #define _CRTAPI2 __cdecl #endif int _CRTAPI2 func();
    • The D Way D方式

      Calling conventions can be specified in blocks, so there's no need to change it for every function:可以在块中指定调用约定,因此无需为每个 function 更改它:

       extern (Windows) { int onefunc(); int anotherfunc(); }
  4. Simple generic programming:简单的泛型编程:

    • The C Preprocessor Way C预处理器方式

      Selecting which function to use based on text substitution:根据文本替换选择要使用的 function:

       #ifdef UNICODE int getValueW(wchar_t *p); #define getValue getValueW #else int getValueA(char *p); #define getValue getValueA #endif
    • The D Way D方式

      D enables declarations of symbols that are aliases of other symbols: D启用作为其他符号别名的符号声明:

       version (UNICODE) { int getValueW(wchar[] p); alias getValueW getValue; } else { int getValueA(char[] p); alias getValueA getValue; }

There are more examples on the DigitalMars website . DigitalMars 网站上有更多示例。

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.它们是 C 之上的一种编程语言(一种更简单的语言),因此它们对于在编译时进行元编程很有用......换句话说,您可以编写生成 C 代码的宏代码,所需的行数和时间更少直接写在C中。

They are also very useful to write "function like" expressions that are "polymorphic" or "overloaded";它们对于编写“多态”或“重载”的“函数式”表达式也非常有用; eg a max macro defined as:例如,一个 max 宏定义为:

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

is useful for any numeric type;适用于任何数字类型; and in C you could not write:在 C 中你不能写:

int max(int a, int b) {return a>b?a:b;}
float max(float a, float b) {return a>b?a:b;}
double max(double a, double b) {return a>b?a:b;}
...

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)...更不用说条件编译和文件包括(也是宏语言的一部分)......

Macros allow someone to modify the program behavior during compilation time.宏允许某人在编译期间修改程序行为。 Consider this:考虑一下:

  • C constants allow fixing program behavior at development time C 常量允许在开发时修复程序行为
  • C variables allow modifying program behavior at execution time C 变量允许在执行时修改程序行为
  • C macros allow modifying program behavior at compilation time C 宏允许在编译时修改程序行为

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.在编译时意味着未使用的代码甚至不会 go 进入二进制文件,并且构建过程可以修改这些值,只要它与宏预处理器集成。 Example: make ARCH=arm (assumes forwarding macro definition as cc -DARCH=arm)示例:make ARCH=arm(假设转发宏定义为 cc -DARCH=arm)

Simple examples: (from glibc limits.h, define the largest value of long)简单例子:(来自glibc limits.h,定义long的最大值)

#if __WORDSIZE == 64
#define LONG_MAX 9223372036854775807L
#else
#define LONG_MAX 2147483647L
#endif

Verifies (using the #define __WORDSIZE) at compile time if we're compiling for 32 or 64 bits.如果我们正在编译 32 位或 64 位,则在编译时验证(使用#define __WORDSIZE)。 With a multilib toolchain, using parameters -m32 and -m64 may automatically change bit size.使用 multilib 工具链,使用参数 -m32 和 -m64 可能会自动更改位大小。

(POSIX version request) (POSIX 版本请求)

#define _POSIX_C_SOURCE 200809L

Requests during compilation time POSIX 2008 support.编译期间的请求 POSIX 2008 支持。 The standard library may support many (incompatible) standards but with this definition, it will provide the correct function prototypes (example: getline(), no gets(), etc.).标准库可能支持许多(不兼容的)标准,但通过此定义,它将提供正确的 function 原型(例如:getline()、无 gets() 等)。 If the library doesn't support the standard it may give an #error during compile time, instead of crashing during execution, for example.例如,如果库不支持该标准,它可能会在编译时给出#error,而不是在执行期间崩溃。

(hardcoded path) (硬编码路径)

#ifndef LIBRARY_PATH
#define LIBRARY_PATH "/usr/lib"
#endif

Defines, during compilation time a hardcode directory.在编译期间定义硬代码目录。 Could be changed with -DLIBRARY_PATH=/home/user/lib, for example.例如,可以使用 -DLIBRARY_PATH=/home/user/lib 进行更改。 If that were a const char *, how would you configure it during compilation?如果那是一个 const char *,你会在编译期间如何配置它?

(pthread.h, complex definitions at compile time) (pthread.h,编译时的复杂定义)

# define PTHREAD_MUTEX_INITIALIZER \
  { { 0, 0, 0, 0, 0, 0, { 0, 0 } } }

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.为了避免真正使事情复杂化并避免暗示编码不良 styles,我不会给出在不同的、不兼容的操作系统中编译的代码示例。 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.最后,想想条件编译在嵌入式系统上的重要性,处理器速度和 memory 是有限的,系统是非常异构的。

Now, if you ask, is it possible to replace all macro constant definitions and function calls with proper definitions?现在,如果您问,是否可以用正确的定义替换所有宏常量定义和 function 调用? The answer is yes, but it won't simply make the need for changing program behavior during compilation go away.答案是肯定的,但它不会简单地使在编译 go 期间更改程序行为的需要消失。 The preprocessor would still be required.仍然需要预处理器。

Remember that macros (and the pre-processor) come from the earliest days of C.请记住,宏(和预处理器)来自 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.它们曾经是执行内联“函数”的唯一方法(因为,当然,inline 是一个非常新的关键字),并且它们仍然是强制内联某些内容的唯一方法。

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.但他们仍然有自己的位置,有时。

Apart from inlining for efficiency and conditional compilation, macros can be used to raise the abstraction level of low-level C code.除了内联提高效率和条件编译外,宏还可用于提高低级 C 代码的抽象级别。 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. C 并没有真正使您免受 memory 和资源管理和数据精确布局的基本细节的影响,并且支持非常有限的 forms 管理大型系统和机制的信息隐藏和机制。 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!使用宏,您不再局限于仅使用 C 语言中的基本结构:您可以定义自己的数据结构和编码结构(包括类和模板!),同时仍然名义上编写 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++. C++ 方面的一个令人印象深刻(而且有点可怕)的例子已经结束: Boost 预处理器库使用C99 / C++98预处理器来构建(相对)安全的编程结构,然后将其扩展到任何底层声明和代码您输入,无论是 C 还是 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 :来自计算机愚蠢

I've seen this code excerpt in a lot of freeware gaming programs for UNIX:我在许多 UNIX 的免费游戏程序中看到了这段代码摘录:

/* /*
* Bit values. * 位值。
*/ */
#define BIT_0 1 #define BIT_0 1
#define BIT_1 2 #define BIT_1 2
#define BIT_2 4 #define BIT_2 4
#define BIT_3 8 #define BIT_3 8
#define BIT_4 16 #define BIT_4 16
#define BIT_5 32 #define BIT_5 32
#define BIT_6 64 #define BIT_6 64
#define BIT_7 128 #define BIT_7 128
#define BIT_8 256 #define BIT_8 256
#define BIT_9 512 #define BIT_9 512
#define BIT_10 1024 #define BIT_10 1024
#define BIT_11 2048 #define BIT_11 2048
#define BIT_12 4096 #define BIT_12 4096
#define BIT_13 8192 #define BIT_13 8192
#define BIT_14 16384 #define BIT_14 16384
#define BIT_15 32768 #define BIT_15 32768
#define BIT_16 65536 #define BIT_16 65536
#define BIT_17 131072 #define BIT_17 131072
#define BIT_18 262144 #define BIT_18 262144
#define BIT_19 524288 #define BIT_19 524288
#define BIT_20 1048576 #define BIT_20 1048576
#define BIT_21 2097152 #define BIT_21 2097152
#define BIT_22 4194304 #define BIT_22 4194304
#define BIT_23 8388608 #define BIT_23 8388608
#define BIT_24 16777216 #define BIT_24 16777216
#define BIT_25 33554432 #define BIT_25 33554432
#define BIT_26 67108864 #define BIT_26 67108864
#define BIT_27 134217728 #define BIT_27 134217728
#define BIT_28 268435456 #define BIT_28 268435456
#define BIT_29 536870912 #define BIT_29 536870912
#define BIT_30 1073741824 #define BIT_30 1073741824
#define BIT_31 2147483648 #define BIT_31 2147483648

A much easier way of achieving this is:实现这一点的一个更简单的方法是:

#define BIT_0 0x00000001 #define BIT_0 0x00000001
#define BIT_1 0x00000002 #define BIT_1 0x00000002
#define BIT_2 0x00000004 #define BIT_2 0x00000004
#define BIT_3 0x00000008 #define BIT_3 0x00000008
#define BIT_4 0x00000010 #define BIT_4 0x00000010
... ...
#define BIT_28 0x10000000 #define BIT_28 0x10000000
#define BIT_29 0x20000000 #define BIT_29 0x20000000
#define BIT_30 0x40000000 #define BIT_30 0x40000000
#define BIT_31 0x80000000 #define BIT_31 0x80000000

An easier way still is to let the compiler do the calculations:更简单的方法仍然是让编译器进行计算:

#define BIT_0 (1) #define BIT_0 (1)
#define BIT_1 (1 << 1) #define BIT_1 (1 << 1)
#define BIT_2 (1 << 2) #define BIT_2 (1 << 2)
#define BIT_3 (1 << 3) #define BIT_3 (1 << 3)
#define BIT_4 (1 << 4) #define BIT_4 (1 << 4)
... ...
#define BIT_28 (1 << 28) #define BIT_28 (1 << 28)
#define BIT_29 (1 << 29) #define BIT_29 (1 << 29)
#define BIT_30 (1 << 30) #define BIT_30 (1 << 30)
#define BIT_31 (1 << 31) #define BIT_31 (1 << 31)

But why go to all the trouble of defining 32 constants?但是为什么 go 还要麻烦定义 32 个常量呢? The C language also has parameterized macros. C 语言也有参数化宏。 All you really need is:您真正需要的是:

#define BIT(x) (1 << (x)) #define BIT(x) (1 << (x))

Anyway, I wonder if guy who wrote the original code used a calculator or just computed it all out on paper.无论如何,我想知道编写原始代码的人是使用计算器还是只是在纸上计算出来。

That's just one possible use of Macros.这只是宏的一种可能用途。

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:以下是宏真正有用的几种情况:

/* Get the number of elements in array 'A'. */
#define ARRAY_LENGTH(A) (sizeof(A) / sizeof(A[0]))

This is a very popular and frequently used macro.这是一个非常流行且经常使用的宏。 This is very handy when you for example need to iterate through an array.例如,当您需要遍历数组时,这非常方便。

int main(void)
{
    int a[] = {1, 2, 3, 4, 5};
    int i;
    for (i = 0; i < ARRAY_LENGTH(a); ++i) {
        printf("a[%d] = %d\n", i, a[i]);
    }
    return 0;
}

Here it doesn't matter if another programmer adds five more elements to a in the decleration.在这里,如果另一个程序员在声明中向a添加五个更多元素并不重要。 The for -loop will always iterate through all elements. for循环将始终遍历所有元素。

The C library's functions to compare memory and strings are quite ugly to use. C 库用于比较 memory 和字符串的函数很难使用。

You write:你写:

char *str = "Hello, world!";

if (strcmp(str, "Hello, world!") == 0) {
    /* ... */
}

or或者

char *str = "Hello, world!";

if (!strcmp(str, "Hello, world!")) {
    /* ... */
}

To check if str points to "Hello, world" .检查str是否指向"Hello, world" I personally think that both these solutions look quite ugly and confusing (especially .strcmp(...) ).我个人认为这两种解决方案看起来都非常丑陋和令人困惑(尤其是.strcmp(...) )。

Here are two neat macros some people (including I) use when they need to compare strings or memory using strcmp / memcmp :这是一些人(包括我)在需要使用strcmp / memcmp比较字符串或 memory 时使用的两个简洁的宏:

/* Compare strings */
#define STRCMP(A, o, B) (strcmp((A), (B)) o 0)

/* Compare memory */
#define MEMCMP(A, o, B) (memcmp((A), (B)) o 0)

Now you can now write the code like this:现在您可以编写如下代码:

char *str = "Hello, world!";

if (STRCMP(str, ==, "Hello, world!")) {
    /* ... */
}

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.宏不应该用来替换函数,但它们还有其他很好的用途。

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).我曾经在一个旧的 C++ 系统上工作,该系统使用插件系统以他自己的方式将参数传递给插件(使用自定义的类似地图的结构)。 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.一些简单的宏被用来处理这个怪癖,并允许我们在插件中使用具有正常参数的真正的 C++ 类和函数而没有太多问题。 All the glue code being generated by macros.所有由宏生成的胶水代码。

Unlike regular functions, you can do control flow (if, while, for,...) in macros.与常规函数不同,您可以在宏中执行控制流(if、while、for、...)。 Here's an example:这是一个例子:

#include <stdio.h>

#define Loop(i,x) for(i=0; i<x; i++)

int main(int argc, char *argv[])
{
    int i;
    int x = 5;
    Loop(i, x)
    {
        printf("%d", i); // Output: 01234
    } 
    return 0;
} 

Given the comments in your question, you may not fully appreciate is that calling a function can entail a fair amount of overhead.鉴于您问题中的评论,您可能不完全理解调用 function 可能需要相当多的开销。 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.宏让程序员保留 function 的抽象(几乎),但避免了 function 调用的昂贵开销。 The inline keyword is advisory, but the compiler may not always get it right. inline 关键字是建议性的,但编译器可能并不总是正确。 The glory and peril of 'C' is that you can usually bend the compiler to your will. “C”的优点和危险在于您通常可以根据自己的意愿弯曲编译器。

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.在你的面包和黄油中,日常应用程序编程这种微优化(避免 function 调用)通常更糟然后没用,但如果你正在编写一个时间关键的 function 由 Z504284C019F1AFDAD38 操作系统调用,那么它可以产生巨大的影响。

It's good for inlining code and avoiding function call overhead.它有利于内联代码并避免 function 调用开销。 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.它对复杂的事情没有用,但对于你想要内联的简单代码行,它还不错。

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):例如(真正的代码,VS 2010 编译器的语法):

for each (auto entry in entries)
{
        sciter::value item;
        item.set_item("DisplayName",    entry.DisplayName);
        item.set_item("IsFolder",       entry.IsFolder);
        item.set_item("IconPath",       entry.IconPath);
        item.set_item("FilePath",       entry.FilePath);
        item.set_item("LocalName",      entry.LocalName);
        items.append(item);
    }

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. DisplayName用作脚本的字符串和编译器的字段名称。 Is that bad?那不好吗? Yes.是的。 If you refactor you code and rename LocalName to RelativeFolderName (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).如果您重构代码并将LocalName重命名为RelativeFolderName (就像我所做的那样)并且忘记对字符串执行相同的操作(就像我所做的那样),脚本将以您意想不到的方式工作(实际上,在我的示例中取决于您是否忘记在单独的脚本文件中重命名该字段,但是如果该脚本用于序列化,那将是一个100%的错误)。

If you use a macro for this, there will be no room for the bug:如果为此使用宏,则该错误将没有空间:

for each (auto entry in entries)
{
#define STR_VALUE(arg) #arg
#define SET_ITEM(field) item.set_item(STR_VALUE(field), entry.field)
        sciter::value item;
        SET_ITEM(DisplayName);
        SET_ITEM(IsFolder);
        SET_ITEM(IconPath);
        SET_ITEM(FilePath);
        SET_ITEM(LocalName);
#undef SET_ITEM
#undef STR_VALUE
        items.append(item);
    }

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 ).其他人可以使用相同的名称(这就是我使用#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.如果您发现使用宏删除复制粘贴的代码不值得,请保留复制粘贴的代码。

By leveraging C preprocessor's text manipulation one can construct the C equivalent of a polymorphic data structure.通过利用 C 预处理器的文本操作,可以构造 C 等效的多态数据结构。 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.使用这种技术,我们可以构建一个可靠的原始数据结构工具箱,可以在任何 C 程序中使用,因为它们利用了 C 语法而不是任何特定实现的细节。

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这里给出了如何使用宏来管理数据结构的详细说明 - http://multi-core-dump.blogspot.com/2010/11/interesting-use-of-c-macros-polymorphic.html

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.否则,您也可以将其用于符号常量,这样您就不必在多个地方编辑相同的值来更改一件小事。

I didn't see anyone mentioning this so, regarding function like macros, eg:我没有看到任何人提到这一点,关于 function 之类的宏,例如:

#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:所以:

When should you use these over a function?什么时候应该在 function 上使用这些?

Almost never, since there's a more readable alternative which is inline , see https://www.greenend.org.uk/rjk/tech/inline.html or 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). Almost never, since there's a more readable alternative which is inline , see https://www.greenend.org.uk/rjk/tech/inline.html or http://www.cplusplus.com/articles/2LywvCM9/ (the第二个链接是 C++ 页面,但据我所知,这一点适用于 c 编译器)。

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.现在,细微的差别是宏由预处理器处理,而内联由编译器处理,但现在没有实际区别。

when is it appropriate to use these?什么时候适合使用这些?

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).目标是在程序运行时获得一些优势,因为 function 之类的宏(和内联函数)是在预处理(或内联的情况下编译)期间完成的代码替换,而不是存在于 memory 中的实际函数,所以没有 function 调用开销(链接页面中的更多详细信息)。

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).严肃地说,谷歌预处理器滥用(你可能会看到一个与#1 结果类似的 SO 问题)。 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.如果我正在编写一个超出 assert() 功能的宏,我通常会尝试查看我的编译器是否真的会内联类似的 function。

Others will argue against using #if for conditional compilation.. they would rather you:其他人会反对使用 #if 进行条件编译.. 他们宁愿你:

if (RUNNING_ON_VALGRIND)

rather than而不是

#if RUNNING_ON_VALGRIND

.. for debugging purposes, since you can see the if() but not #if in a debugger. .. 出于调试目的,因为您可以在调试器中看到 if() 但看不到 #if。 Then we dive into #ifdef vs #if.然后我们深入研究#ifdef 与#if。

If its under 10 lines of code, try to inline it.如果它的代码少于 10 行,请尝试内联它。 If it can't be inlined, try to optimize it.如果不能内联,试着优化一下。 If its too silly to be a function, make a macro.如果它太傻以至于不能成为 function,那就做一个宏。

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:虽然我不是宏的忠实粉丝,并且不再倾向于写太多 C,但根据我目前的任务,这样的事情(显然可能有一些副作用)很方便:

#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))

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.我想扩展可以被认为是方便的。

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

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