views:

937

answers:

6

Hi,

I was reading some code written in C this evening, and at the top of the file was the function-like macro HASH:

#define HASH(fp) (((unsigned long)fp)%NHASH)

This left me wondering, why would somebody choose to implement a function this way using a function-like macro instead of implementing it as a regular vanilla C function? What are the advantages and disadvantages of each implementation?

Thanks a bunch!

+2  A: 

On one hand, macros are bad because they're done by the preprocessor, which doesn't understand anything about the language and does text-replace. They usually have plenty of limitations. I can't see one above, but usually macros are ugly solutions.

On the other hand, they are at times even faster than a static inline method. I was heavily optimizing a short program and found that calling a static inline method takes about twice as much time (just overhead, not actual function body) as compared with a macro.

pavpanchekha
That's good to know. Thank you! Just curious, what tool were you using to optimize your C code? I haven't tried optimization before, and might like to give it a try.
Ralph
Tool? Vim, `gprof`, and the venerable `time` command. `gprof` is a pretty good profiler with low overhead, though you've got to be careful because it doesn't track macros (it doesn't see them; another of macros's downsides).
pavpanchekha
Which version of which compiler were you using, and what level of optimization were you requesting from it?
Jonathan Leffler
`gcc version 4.3.3 (Ubuntu 4.3.3-5ubuntu4)` (g++). The optimization was `-O3`.
pavpanchekha
A: 

The C Preprocessor can be used to create inline functions. In your example, the code will appear to call the function HASH, but instead is just inline code.

The benefits of doing macro functions were eliminated when C++ introduced inline functions. Many older API like MFC and ATL still use macro functions to do preprocessor tricks, but it just leaves the code convoluted and harder to read.

Andrew Keith
A: 

Your example is not really a function at all,

#define HASH(fp) (((unsigned long)fp)%NHASH)
//  this is a cast ^^^^^^^^^^^^^^^
//  this is your value 'fp'       ^^
//  this is a MOD operation          ^^^^^^

I'd think, this was just a way of writing more readable code with the casting and mod opration wrapped into a single macro 'HASH(fp)'


Now, if you decide to write a function for this, it would probably look like,

int hashThis(int fp)
{
  return ((fp)%NHASH);
}

Quite an overkill for a function as it,

  • introduces a call point
  • introduces call-stack setup and restore
nik
very good point!
Ralph
+6  A: 

Macros like that avoid the overhead of a function call.

It might not seem like much. But in your example, the macro turns into 1-2 machine language instructions, depending on your CPU:

  • Get the value of fp out of memory and put it in a register
  • Take the value in the register, do a modulus (%) calculation by a fixed value, and leave that in the same register

whereas the function equivalent would be a lot more machine language instructions, generally something like

  • Stick the value of fp on the stack
  • Call the function, which also puts the next (return) address on the stack
  • Maybe build a stack frame inside the function, depending on the CPU architecture and ABI convention
  • Get the value of fp off the stack and put it in a register
  • Take the value in the register, do a modulus (%) calculation by a fixed value, and leave that in the same register
  • Maybe take the value from the register and put it back on the stack, depending on CPU and ABI
  • If a stack frame was built, unwind it
  • Pop the return address off the stack and resume executing instructions there

A lot more code, eh? If you're doing something like rendering every one of the tens of thousands of pixels in a window in a GUI, things run an awful lot faster if you use the macro.

Personally, I prefer using C++ inline as being more readable and less error-prone, but inlines are also really more of a hint to the compiler which it doesn't have to take. Preprocessor macros are a sledge hammer the compiler can't argue with.

Bob Murphy
wow, you're right, that's way more code to implement it as a function!
Ralph
That's what inline functions are for! :)
LiraNuna
LiraNuna: inline functions are not part of ANSI C90, which is what most compilers support. They're only in C99, or C++
Eli Bendersky
Some compilers supports it as an extension, though.
LiraNuna
C99 is, of course, ten years old by now. Any C compiler that doesn't support it is getting seriously out of date.
Lars Wirzenius
@liw.fi: tell that to Microsoft!
Jonathan Leffler
Btw ... modulo is a HELL of a lot more than 1 instruction on EVERY processor.
Goz
@Goz: The 68000 DIVS and DIVU instructions do a simultaneous 16 bit quotient and remainder, and the 68020 extends that to 32 bits.
Bob Murphy
@Goz: Also, the x86 DIV and IDIV instructions do a simultaneous 8, 16 or 32 bit quotient and remainder (modulo). However, it looks to me like ARM CPUs don't calculate remainder as a single machine-language operation.
Bob Murphy
+3  A: 

The most common (and most often wrong) reason people give for using macros (in "plain old C") is the efficiency argument. Using them for efficiency is fine if you have actually profiled your code and are optimizing a true bottleneck (or are writing a library function that might be a bottleneck for somebody someday). But most people who insist on using them have Not actually analyzed anything and are just creating confusion where it adds no benefit.

Macros can also be used for some handy search-and-replace type substitutions which the regular C language is not capable of.

Some problems I have had in maintaining code written by macro abusers is that the macros can look quite like functions but do not show up in the symbol table, so it can be very annoying trying to trace them back to their origins in sprawling codesets (where is this thing defined?!). Writing macros in ALL CAPS is obviously helpful to future readers.

If they are more than fairly simple substitutions, they can also create some confusion if you have to step-trace through them with a debugger.

Kilo
could you give an example of one of these search-and-replace substitutions that can only be done with macros that you speak of?
Ralph
Code-generating lines like: #define COLORVAL(color,val) int color##_number = val; I wouldn't do this myself too much, but if you need lines for red, blue, green, ..., black, then it can help.
JXG
p.212 of the "Unix Hater's Handbook" (p.248 in the .pdf http://www.simson.net/ref/ugh.pdf, from http://www.cs.washington.edu/homes/weise/uhh-download.html) discusses some related difficulties.
Kilo
+5  A: 

One important advantage of macro-based implementation is that it is not tied to any concrete parameter type. A function-like macro in C acts, in many respects, as a template function in C++ (templates in C++ were born as "more civilized" macros, BTW). In this particular case the argument of the macro has no concrete type. It might be absolutely anything that is convertible to type unsigned long. For example, if the user so pleases (and if they are willing to accept the implementation-defined consequences), they can pass pointer types to this macro.

Anyway, I have to admit that this macro is not the best example of type-independent flexibility of macros, but in general that flexibility comes handy quite often. Again, when certain functionality is implemented by a function, it is restricted to specific parameter types. In many cases in order to apply similar operation to different types it is necessary to provide several functions with different types of parameters (and different names, since this is C), while the same can be done by just one function-like macro. For example, macro

#define ABS(x) ((x) >= 0 ? (x) : -(x))

works with all arithmetic types, while function-based implementation has to provide quite a few of them (I'm implying the standard abs, labs, llabs and fabs). (And yes, I'm aware of the traditionally mentioned dangers of such macro.)

Macros are not perfect, but the popular maxim about "function-like macros being no longer necessary because of inline functions" is just plain nonsense. In order to fully replace function-like macros C is going to need function templates (as in C++) or at least function overloading (as in C++ again). Without that function-like macros are and will remain extremely useful mainstream tool in C.

AndreyT