Macros and Why Not to Use Them
Initiating a Function with a String Macro — Almost
The preprocessor and macros are useful things but don’t go overboard in their use. Aside from the obvious possible disaster (the pre-processor goes berserk and replaces any code in your application with whatever resides in the macro), macros often have side effects that are not clear when they’re invoked. Unlike functions — whose side effects can be detected in the debugger — a macro has no such debugging functionality. Because the pre-processor “copies” the macro’s code into your application, the debugger doesn’t understand how the macro works. And even though macros allow you to avoid the overhead of pushing data onto the stack and popping it off to invoke a function, the macro increases code size by duplicating the same block of code every time it’s used. Of course, these reasons by themselves aren’t enough to make programmers want to avoid using macros. There are much better reasons. For example, consider the min (for “minimum”) macro, which many programs define like this:
Macro issues become subtler when you’re allocating and copying strings — which programmers do in C++ all the time. Here’s the usual scenario: You have an input string, want to make a copy of it, and store the result in another string. A library function, called strdup, does this exact thing. But suppose that you want to copy only a certain number of bytes of the original string into your new string.
Fixing What Went Wrong with the Macro
What happened here? The output of the last function call should have been the same as the first one! This is a serious problem that can be traced to a side effect of the macro. Because the procedure didn’t check to see whether input and output were the same, you cannot safely delete the character-pointer buffer that you didn’t allocate. However, by following the steps given here, you can rewrite this macro as an equivalent — but safer — function.
Using Macros Appropriately
What are macros good for, then? Remember, a macro is nothing more (and nothing less) than syntactical sugar; it’s easy to wind up with too much of a good thing. Using a heap of macros may make reading your coding easier, but you should never modify your code itself with a macro. For example, if you have a particularly complex expression — such as (*iter).c_str() — which can occur when you’re using the Standard Template Library (STL) in C++ — you could create a macro that says.
This capability is also useful when you’re saving an object into memory while performing a global undo function. You save the state of the object each time it’s going to change, and then return it to that saved state by simply copying the block of memory over it. There are other ways to do this task, of course, but this one is simple and very extensible.
Evaluating the Results
There are some interesting conclusions to be made from this output. For example, although some of the results are not surprising at all (for instance, that the size of a character field is 1 byte), some surprises crop up — for example, the size of a character pointer is the same as any other pointer, which turns out to be the size of along. That means the maximum allowable number of bytes you can allocate using standard pointers is 4 bytes worth — 32 bits. (That’s why Microsoft Windows is a 32-bit operating system. But you knew that.)
Last word
The next surprise is lurking among the objects in the list: The size of a string is shown as 4 bytes, which can’t possibly be right — the string it’s storing is longer than that. How can that be? The answer is that the size of the function returns the number of bytes directly allocated by the object — that is, the number of bytes occupied by the private and public variables in the object, plus a few bytes for virtual functions (such as those in the Foo and Bar classes). Notice that even though the Bar class has no member variables, it still takes up 4 bytes because it needs the virtual function table (or v-table) discussed earlier in Technique 2. Now, why does the Foo class take up 1 byte, when it has no virtual methods and no member variables? The answer is the size of the function.