A data model defines the sizes assigned to standard data types. It is important to understand the data models used by your implementation. However, if your code depends on any assumptions not guaranteed by the standard, you should provide static assertions to ensure that your assumptions are valid. (See DCL03-C. Use a static assertion to test the value of a constant expression.) Assumptions concerning integer sizes may become invalid, for example, when porting from a 32-bit architecture to a 64-bit architecture.
Data Type | iAPX86 | IA-32 | IA-64 | SPARC-64 | ARM-32 | Alpha | 64-bit Linux, FreeBSD, |
---|---|---|---|---|---|---|---|
| 8 | 8 | 8 | 8 | 8 | 8 | 8 |
| 16 | 16 | 16 | 16 | 16 | 16 | 16 |
| 16 | 32 | 32 | 32 | 32 | 32 | 32 |
| 32 | 32 | 32 | 64 | 32 | 64 | 64 |
| N/A | 64 | 64 | 64 | 64 | 64 | 64 |
Pointer | 16/32 | 32 | 64 | 64 | 32 | 64 | 64 |
Code frequently embeds assumptions about data models. For example, some code bases require pointer and long
to have the same size, whereas other large code bases require int
and long
to be the same size [van de Voort 2007]. These types of assumptions, while common, make the code difficult to port and make the ports error prone. One solution is to avoid any implementation-defined behavior. However, this practice can result in inefficient code. Another solution is to include either static or runtime assertions near any platform-specific assumptions, so they can be easily detected and corrected during porting.
<limits.h>
Possibly more important than knowing the number of bits for a given type is knowing that limits.h
defines macros that can be used to determine the integral ranges of the standard integer types for any conforming implementation. For example, UINT_MAX
is the largest possible value of an unsigned int
, and LONG_MIN
is the smallest possible value of a long int
.
<stdint.h>
The stdint.h
header introduces types with specific size restrictions that can be used to avoid dependence on a particular data model. For example, int_least32_t
is the smallest signed integer type supported by the implementation that contains at least 32 bits. The type uint_fast16_t
is the fastest unsigned integer type supported by the implementation that contains at least 16 bits. The type intmax_t
is the largest signed integer, and uintmax_t
is the largest unsigned type, supported by the implementation. The following types are required to be available on all implementations:
Smallest Types | Signed | Unsigned |
---|---|---|
8 bits |
|
|
16 bits |
|
|
32 bits |
|
|
64 bits |
|
|
Fastest Types | Signed | Unsigned |
8 bits |
|
|
16 bits |
|
|
32 bits |
|
|
64 bits |
|
|
Largest Types | Signed | Unsigned |
Maximum |
|
|
Additional types may be supported by an implementation, such as int8_t
, a type of exactly 8 bits, and uintptr_t
, a type large enough to hold a converted void *
if such an integer exists in the implementation.
<inttypes.h>
The inttypes.h
header declares functions for manipulating greatest-width integers and converting numeric character strings to greatest-width integers.
This noncompliant example attempts to read a long
into an int
. This code works for models in which sizeof(int) == sizeof(long)
. For others, it causes an unexpected memory write similar to a buffer overflow.
int f(void) { FILE *fp; int x; /* ... */ if (fscanf(fp, "%ld", &x) < 1) { return -1; /* Indicate failure */ } /* ... */ return 0; } |
Some compilers can generate warnings if a constant format string does not match the argument types.
This compliant solution uses the correct format for the type being used:
int f(void) { FILE *fp; int x; /* Initialize fp */ if (fscanf(fp, "%d", &x) < 1) { return -1; /* Indicate failure */ } /* ... */ return 0; } |
This noncompliant code attempts to guarantee that all bits of a multiplication of two unsigned int
values are retained by performing arithmetic in the type unsigned long
. This practice works for some platforms, such as 64-bit Linux, but fails for others, such as 64-bit Microsoft Windows.
unsigned int a, b; unsigned long c; /* Initialize a and b */ c = (unsigned long)a * b; /* Not guaranteed to fit */ |
This compliant solution uses the largest unsigned integer type available if it is guaranteed to hold the result. If it is not, another solution must be found, as discussed in INT32-C. Ensure that operations on signed integers do not result in overflow.
#if UINT_MAX > UINTMAX_MAX/UINT_MAX #error No safe type is available. #endif /* ... */ unsigned int a, b; uintmax_t c; /* Initialize a and b */ c = (uintmax_t)a * b; /* Guaranteed to fit, verified above */ |
Understanding the data model used by your implementation is necessary to avoid making errors about the sizes of integer types and the range of values they can represent. Making assumptions about the sizes of data types may lead to buffer-overflow-style attacks.
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
INT00-C | High | Unlikely | High | P3 | L3 |
Tool | Version | Checker | Description |
---|---|---|---|
Axivion Bauhaus Suite | CertC-INT00 | ||
PC-lint Plus | 559, 705, 706, 2403 | Assistance provided: Reports data type inconsistencies in format strings | |
Polyspace Bug Finder | Checks for:
Rec. partially covered. | ||
PVS-Studio | V629, V5004 |
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
SEI CERT C++ Coding Standard | VOID INT00-CPP. Understand the data model used by your implementation(s) |
ISO/IEC TR 24772:2013 | Bit Representations [STR] |
[Open Group 1997a] |
[van de Voort 2007] |