Typedef your data types!

Data types are the most important part of any C/C++ program - be it embedded systems, firmware or linux. The sad part being, most of my friends and partners take them lightly and forget that at times. They may destroy our lives and explode on fields (no, really). So what's the big deal with them? The biggest problem with data types is their size in terms of memory. Most of the books define the data type "int" to be of 32 bits, i.e. of the size of 4 bytes. So if we declare a variable of type "int" it would occupy 4 bytes of memory:

{
  int variable_1 = 0;
  printf ( "\nSize of variable_1 = %d bytes.", sizeof ( variable_1 ) );
}

They expect it to print "Size of variable_1 = 4 bytes." But is this always true? NO!

The size of the data type "int" totally depends on the compiler that is being used to compile the code. As a matter of fact, it does not even depend on the processor. So if we compile the above code on a 32-bit processor with 16 bit compiler, the code would give an output as:

"Size of variable_1 = 2 bytes."

Similarly, if we compile the above code on a 64 bit processor with a 64 bit compiler, the code would give an output as:

"Size of variable_1 = 8 bytes."

So how do we write our code compiler agnostic? One way is to keep changing the types of all our variables when porting between platforms or compilers. But then that is not what we would like to do. "Smart people do Smart things the Smart way." Be smart!

So here is where we start coding using typedefs of datatypes. We use the following typedefs in place of data types directly:

  1. uint8 for an unsigned data type of size 8 bits
  2. int8 for a signed data type of size 8 bits
  3. uint16 for an unsigned data type of size 16 bits
  4. int16 for a signed data type of size 16 bits
  5. uint32 for an unsigned data type of size 32 bits
  6. int32 for a signed data type of size 32 bits
  7. uint64 for an unsigned data type of size 64 bits
  8. int64 for a signed data type of size 64 bits

This way while coding we have enough understanding of the memory size we are handling.

typedef illustration
Image Credits

Now how to define these new typedefs? There are multiple ways to do so. I generally have a header file called typedef.h where I keep all of my typedefs. In most of the linux based operating systems as well as high level microcontrollers, we can typedef as follows:

/*----------------------------------------------------------------------------*/
/*!@brief typedef for uint64 */
typedef uint64_t uint64;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for int64 */
typedef int64_t int64;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for uint32 */
typedef uint32_t uint32;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for int32 */
typedef int32_t int32;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for uint16 */
typedef uint16_t uint16;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for int16 */
typedef int16_t int16;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for uint8 */
typedef uint8_t uint8;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for int8 */
typedef int8_t int8;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for BOOLEAN */
typedef uint8_t BOOLEAN;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for BYTE */
typedef uint8_t BYTE;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for WORD */
typedef uint32_t WORD;

But life is not that easy! On most of the microcontroller families, such typedef libraries like uint32_t and all are missing (It is defined in stdint.h). On such platforms, we need to figure out the sizes of these data types using the statement sizeof ( <data_type> ) and then define the typedefs. Moreover, we can have more that one microcontroller supporting the code - in that case, we define typedefs based on the microcontroller the code is compiled for. No! it is not that tough, check it out:

#ifdef MICROCONTROLLER_32_BIT_COMPILER_32_BIT
	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for uint64 */
	typedef unsigned long uint64;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for int64 */
	typedef long int64;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for uint32 */
	typedef unsigned int uint32;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for int32 */
	typedef int int32;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for uint16 */
	typedef unsigned short uint16;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for int16 */
	typedef short int16;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for uint8 */
	typedef unsigned char uint8;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for int8 */
	typedef char int8;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for BOOLEAN */
	typedef unsigned char BOOLEAN;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for BYTE */
	typedef unsigned char BYTE;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for WORD */
	typedef unsigned int WORD;

#endif /* MICROCONTROLLER_32_BIT_COMPILER_32_BIT */

#ifdef MICROCONTROLLER_16_BIT_COMPILER_16_BIT
	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for uint32 */
	typedef unsigned long uint32;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for int32 */
	typedef long int32;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for uint16 */
	typedef unsigned int uint16;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for int16 */
	typedef int int16;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for uint8 */
	typedef unsigned char uint8;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for int8 */
	typedef char int8;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for BOOLEAN */
	typedef unsigned char BOOLEAN;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for BYTE */
	typedef unsigned char BYTE;

	/*----------------------------------------------------------------------------*/
	/*!@brief typedef for WORD */
	typedef unsigned long WORD;

#endif /* MICROCONTROLLER_16_BIT_COMPILER_16_BIT */

So in your code, define the macro MICROCONTROLLER_16_BIT_COMPILER_16_BIT when using a 16-bit compiler on a 16-bit processor and define the macro MICROCONTROLLER_32_BIT_COMPILER_32_BIT when using a 32-bit compiler on a 32-bit processor. Make sure to have a check that none of these different macros are defined together. I have already taught how to do that in this post.

Now the beauty of this design is that when you port your code from a 32-bit compiler to a 16-bit compiler, you will not have to change the data types of all your variables based on the new compiler/platform. What automatically happens is:

On 32 bit compiler: uint32 variable_1 = 0; is expanded to unsigned int variable_1 = 0; which is a 32 bit variable. On 16 bit compiler uint32 variable_1 = 0; is expanded to unsigned long variable_1 = 0;, a 32 bit variable

Isn't this a beauty! :-)

Here is my entire typedef.h for a 32-bit microcontroller for your reference:

/*
  @file typedefs.h
  @author Saurav Agarwala
  @created on 08/04/2015

  @brief This file contains the typedefs

  @details This file contains the typedefs
*/

#ifndef TYPEDEFS_H_
#define TYPEDEFS_H_

#ifdef __cplusplus
extern "C" {
#endif

/*==============================================================================
                               INCLUDE FILES
==============================================================================*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
/*==============================================================================
                      DEFINITIONS AND TYPES : MACROS
==============================================================================*/
/*----------------------------------------------------------------------------*/
/*!@brief define NULL */
#ifndef NULL
#define NULL                            ( 0 )         
#endif /* #ifndef NULL */

/*----------------------------------------------------------------------------*/
/*!@brief define TRUE */
#ifndef TRUE
#define TRUE                            ( 1 )         
#endif /* #ifndef TRUE */

/*----------------------------------------------------------------------------*/
/*!@brief define FALSE */
#ifndef FALSE
#define FALSE                           ( 0 )         
#endif /* #ifndef FALSE */

/*----------------------------------------------------------------------------*/
/*!@brief define INIT_INVALID */
#define INIT_INVALID                    ( ~0 )         

/*----------------------------------------------------------------------------*/
/*!@brief define FALSE */
#ifndef PW_INLINE
#define PW_INLINE                       inline
#endif /* #ifndef PW_INLINE */

/*==============================================================================
                      DEFINITIONS AND TYPES : ENUMS
==============================================================================*/

/*==============================================================================
                   DEFINITIONS AND TYPES : STRUCTURES
==============================================================================*/

/*==============================================================================
                           EXTERNAL DECLARATIONS
==============================================================================*/

/*----------------------------------------------------------------------------*/
/*!@brief typedef for uint64 */
typedef unsigned long uint64;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for int64 */
typedef long int64;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for sint64 */
typedef signed long sint64;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for uint32 */
typedef unsigned int uint32;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for int32 */
typedef int int32;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for sint32 */
typedef signed int sint32;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for uint16 */
typedef unsigned short uint16;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for int16 */
typedef short int16;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for sint16 */
typedef signed short sint16;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for uint8 */
typedef unsigned char uint8;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for int8 */
typedef char int8;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for sint8 */
typedef signed char sint8;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for BOOLEAN */
typedef unsigned char BOOLEAN;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for BYTE */
typedef unsigned char BYTE;

/*----------------------------------------------------------------------------*/
/*!@brief typedef for WORD */
typedef unsigned int WORD;

/*==============================================================================
                           FUNCTION PROTOTYPES
==============================================================================*/

#ifdef __cplusplus
} // extern "C"
#endif

#endif /* TYPEDEFS_H_ */

P.S. typedefs are not always appreciated and one can definitely overdo them. Remember, the idea is to simplify, not obfuscate. Read what Linus Torvalds has to say about typedefs here, and here in Linux Coding Guidelines and enjoy a good counterpoint!