views:

1259

answers:

12

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 a 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 werent 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 dont get it. The most common answer is to inline code. If the inline keyword doesnt 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 dont 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.

A: 

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.

Tim Post
+1  A: 

It's good for inlining code and avoid function call overhead. As well as using it if u want to change the behaviour later without editing lots of places. Its not useful for complex things, but for simple lines of code that u want to inline its not bad.

Arkaitz Jimenez
+2  A: 

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.

sykora
+4  A: 

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.

Michael Kohne
A: 

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.

itsmatt
+7  A: 

I end up having to remember what the macro is a 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 werent 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.

Pete Kirkham
Thanks. This is helpful. I can see this as a plausible use for them. It provides easy to use and clear debugging that cannot easily be replaced by a function.
Jack Ryan
+2  A: 

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.

VirtualBlackFox
+1  A: 

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

Pontus Gagge
Why is it better to use the preprocessor to define data structures than it is to use a struct? And surely the point at which you start defining classes and templates using the preprocessor is the point at which you might consider using C++, or another language with support for these constructs.
Jack Ryan
Depends on what degrees of freedom you want to have: macros allow you to produce a whole range of data structures consistently. And you're quite right: it's a last resort, as I wrote. But sometimes, you have to work with the tools you get.
Pontus Gagge
A: 

See this thread from yesterday for examples of things that require macros and that can't be implemented using functions alone.

anon
+5  A: 

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.

Brad Gilbert
The D ways you wrote are unnecessary, most would think.
OTZ
A: 

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.

Charles E. Grant
I can understand that inlining can be useful as an optimisation, but I cannot understand why the preprocessor is needed to do this. Why doesn't the inline keyword always work? Use of the preprocesor to "really inline" seems like a common hack that would be better served by changing the compiler.
Jack Ryan
There is a cost to inlining code. The compiler has some rules of thumb for balancing the benefit (faster code) against the cost (fatter code). If the rule of thumb happens to be wrong in your case, macros let you shove the compiler out of the way to get the result you want.
Charles E. Grant
There are issues of backwards compatibility and historical accident. There are billions of lines of 'C' code that people don't want to modify, so changes to the 'C' language at this point need to be fairly small and as backwards compatible as possible.
Charles E. Grant
A: 

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

fortran