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.
#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.
#ifndef MIN
#define MIN( n1, n2 ) ((n1) > (n2) ? (n2) : (n1))
#endif
#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.
#define ALIGN_SIZE( sizeToAlign, PowerOfTwo ) \
(((sizeToAlign) + (PowerOfTwo) - 1) & ~((PowerOfTwo) - 1))
#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.
#define GET_FIELD_OFFSET( StructName, FieldName ) \
((short)(long)(&((StructName *)NULL)->FieldName))
#define GET_FIELD_PTR( pStruct, nOffset ) \
((void *)(((char *)pStruct) + (nOffset)))
#define ALLOC_STRUCT( StructName ) ((StructName *)malloc( sizeof( StructName )))
#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).
#define IS_ODD( num ) ((num) & 1)
#define IS_EVEN( num ) (!IS_ODD( (num) ))
#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.
#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.
#define 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.
#define IS_LITTLE_ENDIAN() (((*(short *)"21") & 0xFF) == '2')
#define IS_BIG_ENDIAN() (((*(short *)"21") & 0xFF) == '1')
#define IS_DEFAULT_ENDIAN() IS_LITTLE_ENDIAN()
#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; \
} \
}
#define CONVERT_NUM( n ) REVERSE_BYTE_ARRAY( (&(n)), sizeof( n ))
#define CONVERT_NUM16( n ) ((void)(IS_DEFAULT_ENDIAN() ? (n) \
: ((n) = ((((n) & 0x00FF) << 8) | (((n) & 0xFF00) >> 8)))))
#define CONVERT_NUM32( n ) ((void)(IS_DEFAULT_ENDIAN() ? (n) \
: ((n) = ((((n) & 0x000000FF) << 24) | (((n) & 0x0000FF00) << 8) \
| (((n) & 0xFF0000) >> 8) | (((n) & 0xFF000000) >> 24)))))
#define CONVERT_FLOAT( f ) CONVERT_NUM32( (*(long *)&(f) ))
#define CONVERT_DOUBLE( d ) CONVERT_NUM( d )
#define CONVERT_NUM64( n ) CONVERT_NUM( n )