Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Essential Macros for C Programming

4.83/5 (7 votes)
25 Mar 2013CPOL6 min read 24.7K  
Essential macros for C programming

I was extensively using C during 1989-1992. After that, it was C++ for a long period. From 1999, I started coding in Java, then had to use C# for Windows applications.

In mid 2005, I happened to get involved in a project where I had to use C and enjoyed the experience thoroughly. I could use some of the C macros I had written way back in early 1990s and also got an opportunity to implement few which I had conceived but had not implemented.

IMHO, knowledge of C++ and Java enables one to write better C code and even allows OOP concepts to be used. Expertise in C/C++ allows one to enjoy the absence of memory management issues while coding in Java and C#, truly appreciate the convenience provided by the Garbage Collector and at the same time have a watchful concern on the application performance when the Garbage Collector thread starts executing. Though I do not miss the pointers of C and AutoPointers of C++, I definitely miss Operator Overloading in Java. Glad that operator overloading is supported in C# and its delegate feature certainly deserves a thumbs up.

Let me start sharing the C macros with you and hope you would find it useful and informative.

Java and C# have "length" functions to obtain the length of any type of array. We can have similar functionality in C, but it would work only for fixed size arrays and not for dynamic arrays created using malloc or calloc.

C++
///  Obtain the number of elements in the given C array
#define GET_ARRAY_LEN( arrayName ) (sizeof( arrayName ) / sizeof(( arrayName)[ 0 ] ))

MIN and MAX are commonly used macros and in some situations, they might not be defined. It is handy to have them if they are not available.

C++
/// Return min of two numbers. Commonly used but never defined as part of standard headers
#ifndef MIN
#define MIN( n1, n2 )   ((n1) > (n2) ? (n2) : (n1))
#endif

/// Return max of two numbers. Commonly used but never defined as part of standard headers
#ifndef MAX
#define MAX( n1, n2 )   ((n1) > (n2) ? (n1) : (n2))
#endif

Sometimes, when we allocate a memory pool, we might want the size to be a perfect power of two and the following macros could be useful for such cases.

C++
// Aligns the supplied size to the specified PowerOfTwo
#define ALIGN_SIZE( sizeToAlign, PowerOfTwo )       \
        (((sizeToAlign) + (PowerOfTwo) - 1) & ~((PowerOfTwo) - 1))

// Checks whether the supplied size is aligned to the specified PowerOfTwo
#define IS_SIZE_ALIGNED( sizeToTest, PowerOfTwo )  \
        (((sizeToTest) & ((PowerOfTwo) - 1)) == 0)

The second macro is equivalent to ((sizeToTest % PowerOfTwo) == 0). The macro is avoiding the expensive modulo operator and accomplishing the same using bitwise operator. Only if the denominator is an exact power of two, then bitwise AND operator could be used to obtain the remainder.

The first macro is equivalent to (sizeToAlign + PowerOfTwo – 1) / PowerOfTwo * PowerOfTwo. The macro is avoiding the integer division and also the multiplication. Modern optimizing compilers should be able to do the same in most cases, but why take chances when we can do it without much sweat.

We definitely would want to use macros if we need to get the offset of any field that is a member of a structure and also to obtain the address of a field given its offset.

Other useful macros related to struct are ALLOC_STRUCT and INIT_STRUCT. I found that they make the code highly readable, less keystrokes to type and reduce the chance of errors.

C++
// Macros related to "struct"

/// Obtain the offset of a field in a struct
#define GET_FIELD_OFFSET( StructName, FieldName ) \
        ((short)(long)(&((StructName *)NULL)->FieldName))

/// Obtain the struct element at the specified offset given the struct ptr
#define GET_FIELD_PTR( pStruct, nOffset ) \
        ((void *)(((char *)pStruct) + (nOffset)))

/**
Allocates a structure given the structure name and returns a pointer to
that allocated structure.

The main benefit is there is no need to cast the returned pointer, to the
structure type.

@param StructName the name of the structure
@return pointer to allocated structure if successful, else NULL.
@see INIT_STRUCT
*/
#define ALLOC_STRUCT( StructName ) ((StructName *)malloc( sizeof( StructName )))

/**
Initializes the given structure to zeroes using memset().

@param pStruct the pointer to structure that has to be initialized
@see ALLOC_STRUCT
*/
#define INIT_STRUCT( pStruct ) (memset( pStruct, '\0', sizeof( *(pStruct) )))

Here are few macros that are simple, yet useful. I would like to mention my favourite line from the most adored and renowned book, "The C Programming Language" by Brian W Kernighan and Dennis M Ritchie. The quote may not be exact, but I shall try to convey what I have understood.
"Simple is always Elegant. Elegant is always Simple. But no simpler.".

For those who are new to C, "The C Answer Book" is a must have and it contains solutions to the exercises provided in the first book. To mention few more books that I have enjoyed reading, they are "C Traps and Pitfalls", "Writing Solid Code", "Programming Pearls", "More Programming Pearls", "Data Structures and C Programmes", etc.

Here are those simple macros for checking whether a given number is odd or even and whether a number falls between two values (both inclusive).

C++
/// Determine whether the given signed or unsigned integer is odd.
#define IS_ODD( num )   ((num) & 1)

/// Determine whether the given signed or unsigned integer is even.
#define IS_EVEN( num )  (!IS_ODD( (num) ))

/**
Determine whether the given number is between the other two numbers
(both inclusive).
*/
#define IS_BETWEEN( numToTest, numLow, numHigh ) \
        ((unsigned char)((numToTest) >= (numLow) && (numToTest) <= (numHigh)))

The following macro is borrowed from MFC (Microsoft Foundation Classes) to suppress the compiler warnings on unused parameters in a function body.

C++
/**
Use this macro for unused parameters right in the beginning of a function body
to suppress compiler warnings about unused parameters.

This is mainly meant for function parameters and not for unused local variables.
*/
#define UNUSED( ParamName ) \
        ((void)(0 ? ((ParamName) = (ParamName)) : (ParamName)))

Sometimes, we need to use open and close curly braces to have a block without the use of "if", "for" or "while". In C, this is useful if we need to use a local variable which is an array of some significant size and that array is needed only for few lines of code. In such cases, the closing curly brace would make the array to go out of scope resulting in immediate release of the stack memory acquired by the array.

In the case of C++, in addition to a local array, we can have instances of several classes as local variables which can be made to go out of scope using the closing curly brace resulting in the call to destructors of all those class objects thereby releasing all the resources used by those objects.

In such cases, I found it to be extremely useful to use the macros BEGIN_BLOCK and END_BLOCK instead of using the curly braces as it improves the code readability, avoids unnecessary code indentation and clearly broadcasts the intentions about releasing of resources.

C++
/**
To open a "C/C++" block without using any construct such as "if", "for",
"while", etc.

The main purpose of this macro is to improve readability and to make the
intentions clear in the code.

This is useful if some local variables are required only for few lines.
In such cases putting such local variables in a block causes the local
variables to go out of scope and hence reclaim their memory once the end
of block is reached.
*/
#define BEGIN_BLOCK {

/**
Closes a "C/C++" block opened using BEGIN_BLOCK.
*/
#define END_BLOCK   }

Now let me present the endian related macros which I immensely enjoyed implementing them. The memory architecture used by most of the CISC machines is Little-Endian and majority of the RISC architectures use Big-Endian and you can get the comparison of CPU architectures here. These words Little-Endian and Big-Endian are borrowed from Jonathan Swift’s classic, "Gulliver’s Travels". They are the names of the two warring factions of Lilliputians figuring in that timeless satire.

The endianness should be of concern only if we are persisting data to a file and if the file can be used by an application running on an architecture using a different endian. In other words, if a number variable whose size is 16 bits or larger, is written to a file and is read again in the same machine then there is no problem. But if the file has to be read from a different machine whose endian is different, then rearrangements of the bytes becomes necessary. This is applicable to all the number types whose size is larger than one byte and it includes both integer and floating point datatypes.

Java by default uses Big-Endian format while writing or reading from a file. So if the same file is read on different machines by only Java applications, then there is no problem. However, if some other language such as C or C++ is used to read the file written by a Java application or vice versa, then one has to be aware and pay attention to the endianness.

Let us get to the macros. First two macros IS_LITTLE_ENDIAN and IS_BIG_ENDIAN return TRUE or FALSE depending on the current machine’s memory architecture. Then comes the most important macro IS_DEFAULT_ENDIAN where we need to decide and set which one we want to use as default.

After that, various number conversion macros are defined which rearrange the bytes of a supplied number only if the current machine’s endian is different from the default set by us. Every time before we write a number to a file and every time just after a number has been read from a file, these number conversion macros should be used.

What these macros accomplish is that there is no need to supply the machine endianness as part of the compiler options, the same code can be used without any change while compiling on different machines, there is no need to have separate macros for reading and writing numbers and everything we need are provided by macros without using any function call.

Please feel free to nail all the social buttons displayed below this article, appreciate if you leave your comments, opinions or suggestions and in the meantime, I shall start preparing to bring you another blog post.

C++
/**
Determines whether the memory architecture of current processor is LittleEndian.

Optimizing compiler should be able to reduce this macro to a boolean constant
TRUE or FALSE.

@return 1 if LittleEndian, else 0
*/
#define IS_LITTLE_ENDIAN()  (((*(short *)"21") & 0xFF) == '2')

/**
Determines whether the memory architecture of current processor is BigEndian.

Optimizing compiler should be able to reduce this macro to a boolean constant
TRUE or FALSE.

@return 1 if BigEndian, else 0
*/
#define IS_BIG_ENDIAN()     (((*(short *)"21") & 0xFF) == '1')

/**
Change this macro to change the default endian format. In this example,
the default endian format is Little Endian.

Optimizing compiler should be able to reduce this macro to a boolean constant
TRUE or FALSE.

@return 1 if the curren endian format is the default format, else 0
*/
#define IS_DEFAULT_ENDIAN() IS_LITTLE_ENDIAN()

/**
Reverses the bytes of the supplied byte array.
*/
#define REVERSE_BYTE_ARRAY( ByteArray, Size )                               \
        if (!IS_DEFAULT_ENDIAN())                                           \
        {                                                                   \
            int     _i, _j;                                                 \
            char    _cTmp;                                                  \
            for (_i = 0, _j = (Size) - 1; _i < _j; _i++, _j--)              \
            {                                                               \
                _cTmp = ((char *)(ByteArray))[ _i ];                        \
                ((char *)(ByteArray))[ _i ] = ((char *)(ByteArray))[ _j ];  \
                ((char *)(ByteArray))[ _j ] = _cTmp;                        \
            }                                                               \
        }

/**
If the current machine is not default endian, re-arranges the bytes of the
given number. Does nothing if the current machine is default endian.

Use this for number variable whose size is greater than 32 bits.

For 16 and 32 bit numbers CONVERT_NUM16() and CONVERT_NUM32() are recommended.
*/
#define CONVERT_NUM( n )    REVERSE_BYTE_ARRAY( (&(n)), sizeof( n ))

/**
If the current machine is not default endian, re-arranges the bytes of the
given 16-bit number. Does nothing if the current machine is default endian.
*/
#define CONVERT_NUM16( n )  ((void)(IS_DEFAULT_ENDIAN() ? (n)       \
        : ((n) = ((((n) & 0x00FF) << 8) | (((n) & 0xFF00) >> 8)))))

/**
If the current machine is not default endian, re-arranges the bytes of the
given 32-bit number. Does nothing if the current machine is default endian.
*/
#define CONVERT_NUM32( n )  ((void)(IS_DEFAULT_ENDIAN() ? (n)               \
        : ((n) = ((((n) & 0x000000FF) << 24) | (((n) & 0x0000FF00) << 8)    \
        | (((n) & 0xFF0000) >> 8) | (((n) & 0xFF000000) >> 24)))))

/**
If the current machine is not default endian, re-arranges the bytes of the
given 32-bit floating point number. Does nothing if the current machine is
default endian.
*/
#define CONVERT_FLOAT( f )  CONVERT_NUM32( (*(long *)&(f) ))

/**
If the current machine is not default endian, re-arranges the bytes of the
given 64-bit floating point number. Does nothing if the current machine is
default endian.
*/
#define CONVERT_DOUBLE( d ) CONVERT_NUM( d )

/**
If the current machine is not default endian, re-arranges the bytes of the
given 64-bit point number. Does nothing if the current machine is
default endian.
*/
#define CONVERT_NUM64( n )  CONVERT_NUM( n )

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)