Determining Integer Characteristics in C
According to the C language standard, the characteristics of integer data types, particularly their sizes and the ranges of representable values, can vary depending on the compiler implementation and the platform in use.
To ensure code portability and to write robust programs, it is important to be able to determine these characteristics programmatically. In this lesson we will explore how to obtain this information using the <limits.h> header from the standard C library.
- The characteristics of integer data types in C, such as sizes and value ranges, can vary among different compiler implementations.
- The
<limits.h>header provides a series of macros that define the minimum and maximum limits of supported integer data types. - The macros defined in
<limits.h>include limits for types such aschar,short,int,long, andlong long, both signed and unsigned. - Using these macros allows writing portable and robust code, adapting to the specific characteristics of the execution platform.
The <limits.h> header
The standard C library provides the <limits.h> header, which defines a series of macros that represent the minimum and maximum limits of integer data types supported by the compiler and the platform in use. These macros are useful for writing portable code that must adapt to the specific characteristics of the execution environment.
This header does not define any functions, but only constant macros. We can divide the set of defined macros into two main categories:
- Macros for
chartypes and variants (signed char,unsigned char). - Macros for standard integer types (
short,int,long,long longand their unsigned variants).
Let's start with the first group. The macros related to char types are shown in the following table:
| Macro | Value | Description |
|---|---|---|
CHAR_BIT |
Number of bits per byte | |
SCHAR_MIN |
Minimum value for signed char |
|
SCHAR_MAX |
Maximum value for signed char |
|
UCHAR_MAX |
Maximum value for unsigned char |
|
CHAR_MIN |
Varies | Minimum value for char. This value depends on whether char is considered signed or unsigned. The minimum value will be equal to SCHAR_MIN or 0 respectively. |
CHAR_MAX |
Varies | Maximum value for char. This value depends on whether char is considered signed or unsigned. The maximum value will be equal to SCHAR_MAX or UCHAR_MAX respectively. |
MB_LEN_MAX |
Maximum number of bytes needed to represent a multibyte character (we will study multibyte characters in a later chapter). |
Let's now move to the macros related to standard integer types. The following table shows these macros:
| Macro | Value | Formula | Description |
|---|---|---|---|
SHRT_MIN |
Minimum value for short |
||
SHRT_MAX |
Maximum value for short |
||
USHRT_MAX |
Maximum value for unsigned short |
||
INT_MIN |
Minimum value for int |
||
INT_MAX |
Maximum value for int |
||
UINT_MAX |
Maximum value for unsigned int |
||
LONG_MIN |
Minimum value for long |
||
LONG_MAX |
Maximum value for long |
||
ULONG_MAX |
Maximum value for unsigned long |
||
LLONG_MIN |
Minimum value for long long |
||
LLONG_MAX |
Maximum value for long long |
||
ULLONG_MAX |
Maximum value for unsigned long long |
The macros defined within the <limits.h> header are extremely useful for writing portable and robust code, as they allow adapting to the specific characteristics of the execution environment without having to make rigid assumptions about data type limits. For example, we can use these macros to verify whether an int integer is capable of storing a certain value before assigning it, thus avoiding overflow or undefined behaviors.
Here is an example of using <limits.h> macros:
#include <limits.h>
#if INT_MAX < 100000
#error "The int type cannot represent the value 100000"
#endif
In this example, we use the INT_MAX macro to verify whether the int type can represent the value 100000. If INT_MAX is less than 100000, a compilation error is generated and the program is not compiled.
The macros can even be used to create conditional code that adapts to platform characteristics. In fact, we could use conditional compilation to choose which type to use based on the limits defined in <limits.h>.
For example, suppose we need to write a program that stores integer numbers at most as large as 1000000, that is, one million. These numbers must be stored in an integer type variable, which is called quantity. However, we do not know in advance whether the int type is capable of storing such a large number, since its range depends on the platform.
If the platform supports an int type with a sufficient range, we can use int. Otherwise, we must use a larger type, such as long or long long.
For this purpose, we can use the <limits.h> macros to select the most suitable type at compile time:
#include <limits.h>
#if INT_MAX >= 1000000
typedef int quantity_t;
#elif LONG_MAX >= 1000000
typedef long quantity_t;
#elif LLONG_MAX >= 1000000
typedef long long quantity_t;
#else
#error "No available integer type can represent the value 1000000"
#endif
/* ... */
quantity_t quantity;
In this example, we use conditional compilation to define a new type quantity_t, using the typedef declaration. After verifying the limits of int, long, and long long, we choose the smallest type that can represent the value 1000000. If none of the available types can represent such a value, a compilation error is generated.
Subsequently, we can use the quantity_t type to declare the quantity variable, which will be of the most suitable type based on the compilation platform.