C 宏有什么用?

发布于 2024-07-15 03:04:37 字数 563 浏览 7 评论 0 原文

我写了一点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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

如果没有 2024-07-22 03:04:38

鉴于您问题中的评论,您可能没有完全意识到调用函数可能会带来相当大的开销。 参数和关键寄存器可能必须在传入时复制到堆栈,并在传出时展开堆栈。 对于较旧的英特尔芯片尤其如此。 宏让程序员(几乎)保留函数的抽象,但避免了函数调用的昂贵开销。 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.

纵山崖 2024-07-22 03:04:38

与常规函数不同,您可以在宏中控制流程(if、while、for...)。 这是一个例子:

#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;
} 

Unlike regular functions, you can do control flow (if, while, for,...) in macros. 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;
} 
漆黑的白昼 2024-07-22 03:04:38

它有利于内联代码并避免函数调用开销。 如果您想稍后更改行为而不编辑很多地方,也可以使用它。 它对于复杂的事情没有用处,但是对于您想要内联的简单代码行来说,它还不错。

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.

儭儭莪哋寶赑 2024-07-22 03:04:38

通过利用 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

空名 2024-07-22 03:04:38

宏可以让你摆脱复制粘贴的片段,这是你无法通过任何其他方式消除的。

例如(真实代码,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);
    }

这是你传递的地方将同名的字段值放入脚本引擎中。 这是复制粘贴的吗? 是的。 DisplayName 用作脚本的字符串和编译器的字段名称。 那不好吗? 是的。 如果您重构代码并将 LocalName 重命名为 RelativeFolderName (就像我所做的那样),并且忘记对字符串执行相同的操作(就像我所做的那样),则脚本将在以您意想不到的方式(事实上,在我的示例中,这取决于您是否忘记在单独的脚本文件中重命名该字段,但如果该脚本用于序列化,则这将是 100% 的错误)。

如果您为此使用宏,则该错误将没有空间:

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);
    }

不幸的是,这为其他类型的错误打开了大门。 您可以在编写宏时犯一个拼写错误,但永远不会看到损坏的代码,因为编译器不会显示所有预处理后的样子。 其他人可以使用相同的名称(这就是为什么我使用 #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):

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. 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).

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). 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.

九命猫 2024-07-22 03:04:38

明显的原因之一是,通过使用宏,代码将在编译时扩展,并且您可以获得伪函数调用,而无需调用开销。

否则,您还可以将它用于符号常量,这样您就不必在多个位置编辑相同的值来更改一件小事情。

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.

冷︶言冷语的世界 2024-07-22 03:04:38

宏 .. 当您的 &#(*$& 编译器拒绝内联某些内容时。

这应该是一个励志海报,不是吗?

严肃地说,谷歌 预处理器滥用(您可能会看到与 #1 结果类似的 SO 问题)。其他

人会反对使用 #if 进行条件编译.. 他们宁愿你:

if (RUNNING_ON_VALGRIND)

而不是

#if RUNNING_ON_VALGRIND

.. 出于调试目的,因为你可以在调试器中看到 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:

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. 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.

夏天碎花小短裙 2024-07-22 03:04:38

虽然我不太喜欢宏,也不再倾向于编写太多 C 语言,但根据我当前的任务,像这样的东西(显然可能会产生一些副作用)很方便:

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

现在我还没有写任何东西多年来一直如此,但这样的“功能”遍布我职业生涯早期维护的代码中。 我想扩展也算方便吧。

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:

#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.

帅哥哥的热头脑 2024-07-22 03:04:38

我没有看到有人提到这一点,关于宏等函数,例如:

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

When should you use these over a 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).

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).

痕至 2024-07-22 03:04:37

我最终不得不记住宏是什么,并在阅读时将其替换到我的脑海中。

这似乎对宏的命名反映不佳。 我假设如果它是 log_function_entry() 宏,您就不必模拟预处理器。

我遇到的那些直观易懂的总是像小迷你函数,所以我一直想知道为什么它们不仅仅是函数。

通常它们应该是这样,除非它们需要对通用参数进行操作。

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

将适用于带有 < 运算符的任何类型。

宏不仅仅是函数,还允许您使用源文件中的符号执行操作。 这意味着您可以创建新的变量名称,或引用宏所在的源文件和行号。

在 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)

,其中格式的工作方式类似于 printf。 如果守卫为真,它将输出消息以及打印该消息的文件和行号。 如果它是一个函数调用,它不会知道您调用它的文件和行,并且使用 vaprintf 会做更多的工作。

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.

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

#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. 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.

戏蝶舞 2024-07-22 03:04:37

通过比较 C 宏的几种使用方式以及如何在 D 中实现它们,这段摘录基本上总结了我对此事的看法。

复制自 DigitalMars.com

C发明时,编译器
技术很原始。 安装一个
文本宏预处理器放在前面
结束是一个简单而简单的方法
添加许多强大的功能。 这
增加尺寸和 的复杂性
计划表明,这些
特性具有许多固有的
问题。 D 没有
预处理器; 但 D 提供了更多
可扩展意味着解决相同的问题
问题。

预处理器宏为 C 添加了强大的功能和灵活性。 但它们有一个缺点:

  • 宏没有范围的概念; 它们从定义点到源代码结束都是有效的。 它们跨越 .h 文件、嵌套代码等。当 #include 包含数万行宏定义时,避免无意的宏扩展就会出现问题。
  • 调试器不知道宏。 尝试使用符号数据调试程序会受到调试器只知道宏扩展而不知道宏本身的破坏。
  • 宏使得源代码无法标记化,因为早期的宏更改可以任意重做标记。
  • 宏的纯文本基础导致任意和不一致的使用,使得使用宏的代码容易出错。 (一些解决这个问题的尝试是通过 C++ 中的模板引入的。)
  • 宏仍然用于弥补语言表达能力的缺陷,例如用于头文件的“包装器”。

下面列举了宏的常见用途以及 D 中的相应功能:

  1. 定义文字常量:

    • C 预处理器方式

      <前><代码>#define VALUE 5

    • D 方式

      const int VALUE = 5; 
        
  2. 创建值或标志列表:

    • C 预处理器方式

      int 标志: 
        #定义FLAG_X 0x1 
        #定义FLAG_Y 0x2 
        #定义FLAG_Z 0x4 
        ... 
        标志 |= FLAG_X; 
        
    • D 方式

      枚举标志 { X = 0x1, Y = 0x2, Z = 0x4 }; 
        标志标志; 
        ... 
        标志 |= FLAGS.X; 
        
  3. 设置函数调用约定:

    • C 预处理器方式

      <前><代码>#ifndef _CRTAPI1
      #define _CRTAPI1 __cdecl
      #万一
      #ifndef_CRTAPI2
      #define _CRTAPI2 __cdecl
      #万一

      int _CRTAPI2 func();

    • D 方式

      可以在块中指定调用约定,因此无需为每个函数更改它:

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

    • C 预处理器方式

      根据文本替换选择要使用的函数:

      <前><代码>#ifdef UNICODE
      int getValueW(wchar_t *p);
      #define getValue getValueW
      #别的
      int getValueA(char *p);
      #define getValue getValueA
      #万一

    • D 方式

      D 允许将符号声明为其他符号的别名:

      版本 (UNICODE) 
        { 
            int getValueW(wchar[] p); 
            别名 getValueW getValue; 
        } 
        别的 
        { 
            int getValueA(char[] p); 
            别名 getValueA getValue; 
        } 
        

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 in D.

copied from DigitalMars.com

Back when C was invented, compiler
technology was primitive. 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; but D provides a more
scalable means to solve the same
problems.

Macros

Preprocessor macros add powerful features and flexibility to C. But they have a downside:

  • Macros have no concept of 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.
  • 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++.)
  • Macros are still used to make up for deficits in the language's expressive capability, such as for "wrappers" around header files.

Here's an enumeration of the common uses for macros, and the corresponding feature in D:

  1. Defining literal constants:

    • The C Preprocessor Way

      #define VALUE 5
      
    • The D Way

      const int VALUE = 5;
      
  2. Creating a list of values or flags:

    • The C Preprocessor Way

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

      enum FLAGS { X = 0x1, Y = 0x2, Z = 0x4 };
      FLAGS flags;
      ...
      flags |= FLAGS.X;
      
  3. Setting function calling conventions:

    • The C Preprocessor Way

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

      Calling conventions can be specified in blocks, so there's no need to change it for every function:

      extern (Windows)
      {
          int onefunc();
          int anotherfunc();
      }
      
  4. Simple generic programming:

    • The C Preprocessor Way

      Selecting which function to use based on text substitution:

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

      D enables declarations of symbols that are aliases of other symbols:

      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.

混浊又暗下来 2024-07-22 03:04:37

它们是一种基于 C 的编程语言(更简单的语言),因此它们对于在编译时进行元编程非常有用……换句话说,您可以编写宏代码,以更少的行数和时间生成 C 代码直接用 C 语言编写。

它们对于编写“多态”或“重载”的“类函数”表达式也非常有用; 例如, max 宏定义为:

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

对于任何数字类型都很有用; 在 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;}
...

即使你想,因为你不能重载函数。

更不用说条件编译和文件包含(这也是宏语言的一部分)......

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:

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

is useful for any numeric type; and in C you could not write:

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)...

攒一口袋星星 2024-07-22 03:04:37

宏允许某人在编译期间修改程序行为。 考虑一下:

  • C 常量允许在开发时修复程序行为
  • C 变量允许在执行时修改程序行为
  • C 宏允许在编译时修改程序行为 在编译

时意味着未使用的代码甚至不会进入二进制文件,并且构建过程可以修改值,只要它与宏预处理器集成即可。 示例: make ARCH=arm (假设转发宏定义为 cc -DARCH=arm)

简单示例:
(来自 glibc limit.h,定义 long 的最大值)

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

如果我们正在编译 32 位或 64 位,则在编译时验证(使用 #define __WORDSIZE)。 对于 multilib 工具链,使用参数 -m32 和 -m64 可能会自动更改位大小。

(POSIX 版本请求)

#define _POSIX_C_SOURCE 200809L

编译时请求 POSIX 2008 支持。 标准库可能支持许多(不兼容)标准,但通过此定义,它将提供正确的函数原型(例如:getline()、no gets() 等)。 例如,如果库不支持该标准,它可能会在编译时给出#error,而不是在执行期间崩溃。

(硬编码路径)

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

在编译期间定义硬编码目录。 例如,可以使用 -DLIBRARY_PATH=/home/user/lib 进行更改。 如果那是 const char *,您将如何在编译期间配置它?

(pthread.h,编译时的复杂定义)

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

可能会声明大段文本,否则不会被简化(始终在编译时)。 使用函数或常量(在编译时)不可能做到这一点。

为了避免让事情真正复杂化并避免提出糟糕的编码风格,我不会给出在不同的、不兼容的操作系统中编译的代码示例。 为此,请使用交叉构建系统,但应该清楚的是,预处理器允许在没有构建系统帮助的情况下实现这一点,并且不会因为缺少接口而中断编译。

最后,考虑一下条件编译在嵌入式系统上的重要性,在嵌入式系统中,处理器速度和内存有限,而且系统非常异构。

现在,如果您问,是否可以用正确的定义替换所有宏常量定义和函数调用? 答案是肯定的,但它不会简单地消除在编译期间更改程序行为的需要。 仍然需要预处理器。

Macros allow someone to modify the program behavior during compilation time. Consider this:

  • C constants allow fixing program behavior at development time
  • C variables allow modifying program behavior at execution time
  • C macros allow modifying program behavior at compilation time

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)

#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. With a multilib toolchain, using parameters -m32 and -m64 may automatically change bit size.

(POSIX version request)

#define _POSIX_C_SOURCE 200809L

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)

#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. If that were a const char *, how would you configure it during compilation ?

(pthread.h, complex definitions at compile time)

# 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. 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.

因为看清所以看轻 2024-07-22 03:04:37

请记住,宏(和预处理器)来自 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.

猫卆 2024-07-22 03:04:37

除了内联以提高效率和条件编译之外,宏还可用于提高低级 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...!

软糯酥胸 2024-07-22 03:04:37

来自计算机愚蠢

我在很多 UNIX 的免费游戏程序中都看到过这段代码摘录:

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

实现这一点的更简单的方法是:

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

更简单的方法仍然是让编译器进行计算:

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

但是为什么要这么麻烦地定义 32 个常量呢? C语言也有参数化宏。 您真正需要的是:

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

无论如何,我想知道编写原始代码的人是否使用计算器或只是在纸上计算出来。

这只是宏的一种可能用途。

From Computer Stupidities:

I've seen this code excerpt in a lot of freeware gaming programs for UNIX:

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

A much easier way of achieving this is:

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

An easier way still is to let the compiler do the calculations:

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

But why go to all the trouble of defining 32 constants? The C language also has parameterized macros. All you really need is:

#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.

最后的乘客 2024-07-22 03:04:37

我将补充已经说过的内容。

因为宏适用于文本替换,所以它们允许您执行使用函数无法执行的非常有用的操作。

以下是宏真正有用的一些情况:

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

这是一个非常流行且经常使用的宏。 例如,当您需要迭代数组时,这非常方便。

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;
}

在这里,如果另一个程序员在减法中向 a 添加五个元素并不重要。 for 循环将始终遍历所有元素。

C 库的比较内存和字符串的函数使用起来相当难看。

您编写:

char *str = "Hello, world!";

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

char *str = "Hello, world!";

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

检查 str 是否指向 "Hello, world"。 我个人认为这两个解决方案看起来都相当丑陋且令人困惑(尤其是 !strcmp(...))。

这是一些人(包括我)需要使用 strcmp/memcmp 比较字符串或内存时使用的两个简洁的宏:

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

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

现在您可以编写如下代码:

char *str = "Hello, world!";

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

这里意图是不是清晰多了!

这些是宏用于函数无法完成的事情的情况。 宏不应该用来替换函数,但它们还有其他很好的用途。

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. The for-loop will always iterate through all elements.

The C library's functions to compare memory and strings are quite ugly to use.

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". 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:

/* 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.

云淡风轻 2024-07-22 03:04:37

宏真正发挥作用的情况之一是用它们进行代码生成。

我曾经在一个旧的 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文