What are C macros useful for?

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.


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


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.


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.