How To: Use C Macros Efficiently in Your Code
Copy from:http://www.lainoox.com/how-to-use-c-macros-efficiently-in-your-code/
Any experienced programmer can relate to sprinkling their code with printf statements to try and figure out a NULL pointer, or perhaps whether a function has been reached or not. This is the first step towards debugging code. Further down the line can include using GDB or Valgrind to check code entry paths, modify variables during runtime or check memory usage or look for memory leaks from non-free malloc’d data upon program exit. These tools are usually pulled out when the going gets tough! As an initial pass during basic unit testing debug information is extremely handy. What I usually attempt to do is create a header file (*.h) that can be included in any (*.c) files I happen to be working on. This way, if I modify my macro it only requires a change in one place to complete. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <stdio.h> #include <stdlib.h> #ifdef DEBUG #define DEBUGP(x, args ...) fprintf(stderr, " [%s(), %s:%u]\n" \ x, __FUNCTION__, __FILE__, __LINE__, ## args) #else #define DEBUGP(x, args ...) #endif void calculation() { DEBUGP("data: %u\n",5 * 5); } int main() { DEBUGP("Program started.\n"); calculation(); return 0; } |
Now every time we want to output any debug information we can call DEBUGP(..data..);. In doing so, we also get the file name, the function it was called in, and what line it occurred at. This is extremely helpful for debugging purposes. This macro utilizes the built-in C macros of __LINE__, __FUNCTION__, and __FILE__. Another piece to note is, I have #ifdef DEBUG, this means that if this variable is defined then we can reference DEBUGP, if the DEBUG variable is not defined then we call our “#define DEBUGP(x, args …)“, which does not print out any debug data. This means during compilation we can pass the -DDEBUG flag to the compiler, meaning we can turn on/off our debugging flag effortlessly.
So putting this all together, lets compile the program with the flag enabled.
erik@debian:/debug$ gcc -DDEBUG -o debug debug.c
Now that it is compiled lets run it.
1 2 3 |
erik@debian:/debug$./debug [main(), debug.c:18] Program started. [calculation(), debug.c:12] data: 25 |
So as we can see, with a little bit of organization and taking advantage of C macros we can make debugging a lot easier for ourselves!
How To: Use C Macros Efficiently in Your Code
Earlier I wrote about using C debugging macros when designing and unit testing your code. This how-to will outline other uses of C Macros.
1. Using C Macros for the so-called Magic Number
One great way of taking advantage of the C Preprocessor is to use Macros for any numbers in your code that you may want to change in the future, and is referenced multiple times in your code. This saves time because if that variable value needs to be changed, it can be done in only one place. For instance:
#define MAX_PACKET_SIZE 65535
|
Thus if you were allocating a buffer size, you only need to modify that value. Macros are also usually defined at the top of your code (it must be defined at an earlier line than when you plan to reference it, unless you have it defined in your header file).
So if you had something like this as a ridiculous example:
1 2 3 4 5 |
data_buffer[MAX_PACKET_SIZE]; for(i = 0; i < MAX_PACKET_SIZE; i++){ fprintf(stderr, "%u\n", MAX_PACKET_SIZE); } |
You now have to only modify the #define set earlier and the preprocessor will replace every reference to MAX_PACKET_SIZE with that value.
2. Using C Macros for Basic Functions
Lets say you knew you needed to a lot of multiplication in your program.
#define MULT(x,y) (x) * (y)
|
Now you can call MULT(5,6) which will spit out 30.
3. C Macro to Comment Out Code
Another very handy use of C macros is when commenting out code. If you have already commented out a block of code using /* … */ then within that block you have another set of /* … */ and perhaps a few //. You can actually just do:
1 2 3 4 5 6 7 8 |
#if 0 /* comment ... */ // code /* comment */ #endif |
This will actually remove all code during compilation that exists between your definitions.
4. Using C Macros to Compile for Target Architecture(s)
Sometimes you may need to compile a certain block of code if running on a Win32 machine versus a Unix based environment. For example:
1 2 3 4 5 |
#ifdef WIN32 print(stdout, "Hello\n"); #else fprintf(stderr, "Yay Linux Machine\n"); #endif |
This way you can pass in -DWIN32 to gcc to compile your program for a Windows machine.
5. Using a C Macro to Swap Two Variables
A very handy swap function can also be written in Macro form that will swap the values of two variables. This can become handy if implementing a various sorting algorithms where values must be swapped.
1 2 3 4 5 6 7 8 9 10 |
/* * Swaps two values. * Requires tmp variable to be defined. */ #define SWAP(x, y) \ do { \ tmp = x; \ x = y; \ y = tmp; } \ while (0) |
Wrapping this in a do-while ensures that our swap function is executed only once.
Using Macros in your C program can be useful at times, especially to rid your code of magic numbers with basic #defines. When creating functions for basic evaluation can be handy but it has its draw backs. There is a great article here outlining some of the pitfalls of C macros. As long as you are aware of what you are doing C macros can be very handy.
I often find myself copying and pasting C macros from one program to another. Evidently I end up using these macros more often than I thought. So I thought I would share these useful macros with you. I have written about C macros earlier, C debugging macros and a guide to using C macros in your code. I find them quite useful, but how do macros affect the final product?
A macro (if not C99) is actually an inline expansion of your code. This means that the compiler will insert this code block directly into every spot you refer to the macro, rather than referencing the code block via a memory location. This can save pointer referencing and in some cases your code will be more efficient. You cannot, however, ensure this is the case the compiler may make decisions that it believes will be more efficient. None the less here is a list of some useful macros.
A Macro To Detect File Presence
Rather than attempting to open a file, you can use this macro:
#define exists(filename) (!access(filename, F_OK))
|
In your code you can then do something like:
1 2 3 |
if(exists("data.txt")) .. open file ... else |
Get The Size of An Array of Arbitrary Type
There are some instances where you don’t actually know the size of your array. This handy macro will tell you the size of your array, i.e. how many elements are in it:
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
This example utilizes the sizeof operator, for more information on this refer to this article. You can use this macro as such:
1 2 |
int data[50]; fprintf(stdout, "Array size: %u\n",ARRAY_SIZE(data)); |
Min and Max
The always handy minimum and maximum macros will return the smaller or the larger of two items. These items can be of an arbitrary type, as long as they are the same type.
1 2 3 4 5 6 7 8 9 10 11 12 |
#define min(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x < _y ? _x : _y; }) #define max(x,y) ({ \ typeof(x) _x = (x); \ typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x > _y ? _x : _y; }) |
To use these macros, is much like any other:
1 2 3 4 5 6 7 8 9 10 11 12 |
int main() { int a,b; a=5; b=12; fprintf(stdout, "%u\n", min(a,b)); return 0; } $ ./min 5 |
Voila, here are a few of the macros I find myself commonly using.
PS:Some other good macro usage articles: