Primary Data Types in C¶
The choice of data type in C significantly impacts both performance and memory usage.
For example, using types that match the system's word size (e.g., int
on
a 32-bit system) can enhance performance due to alignment with the CPU architecture.
Choosing the smallest data type that can hold the required range of
values, such as uint8_t
for small ranges, can reduce memory usage.
Developers might optimize memory by carefully selecting appropriate
types, using enum
and bit fields in struct
s for compact storage,
and leveraging pointers and dynamic allocation to manage memory efficiently
NOTE: The use of the _t
suffix is not POSIX-compliant¶
The _t
suffix is reserved by POSIX.
So, if you create a typedef in your program with _t
, it may end
up conflicting with a typedef
in a header in the future
You can zero initialise a struct with an empty {}
as of C23.
Table of Contents¶
- Type Qualifiers
- Size and Range
- Standard Types in C
- Floats vs Doubles
- Precise Types
- Derived Data Types
- Typedef
- Fixed-Width Integer Types
Type Qualifiers¶
const
: Specifies that a variable's value cannot be modified.volatile
: Tells the compiler that a variable can be changed in ways not explicitly specified by the program.register
: Hints to the compiler that a variable should be stored in a CPU register for faster access.restrict
: A pointer qualifier introduced in C99, indicating that the pointer is the only means by which the program will access the object it points to.
Size and Range¶
The exact size and range of data types can vary based on the compiler and the architecture (32-bit vs. 64-bit systems).
Use the sizeof
operator to find the size of a type, and include <limits.h>
and <float.h>
to get constants defining the limits of the types.
Standard Types in C¶
Standard Integer Types¶
The standard C integer types have two versions:
- Signed: Can represent both positive and negative numbers.
- Unsigned: Can only represent positive numbers. Doubles max value.
Signed:¶
-
int
:- Typical Size: 4 bytes (32 bits)
- Range: -2,147,483,648 to 2,147,483,647
-
short int
(short
):- Typical Size: 2 bytes (16 bits)
- Range: -32,768 to 32,767
-
long int
(long
):- Typical Size: 8 bytes (64 bits) on 64-bit systems
- Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
-
long long int
(long long
):- Typical Size: 8 bytes (64 bits)
- Range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
Unsigned:¶
-
unsigned int
:- Typical Size: 4 bytes (32 bits)
- Range: 0 to 4,294,967,295
-
unsigned short
:- Typical Size: 2 bytes (16 bits)
- Range: 0 to 65,535
-
unsigned long
:- Typical Size: 8 bytes (64 bits) on 64-bit systems
- Range: 0 to 18,446,744,073,709,551,615
-
unsigned long long
:- Typical Size: 8 bytes (64 bits)
- Range: 0 to 18,446,744,073,709,551,615
Standard Floating-Point Types:¶
-
float
:- Size: 4 bytes (32 bits)
- Precision: Typically provides ~7 decimal digits of precision.
- Range: Approximately ±3.4E±38 (IEEE 754 standard for single precision).
-
double
:- Size: 8 bytes (64 bits)
- Precision: Typically provides ~15 decimal digits of precision.
- Range: Approximately ±1.7E±308 (IEEE 754 standard for double precision).
-
long double
:- Size and Precision: Vary by system; usually at least as long
as
double
, potentially 12, 16 bytes or more. - Range and Precision: Greater than
double
, specifics depend on the compiler and architecture. - It may provide up to ~19 to 34 decimal digits of precision, with
a correspondingly wider range than
double
.
- Size and Precision: Vary by system; usually at least as long
as
Character Type:¶
char
:- Size: 1 byte (8 bits).
- Range (Signed): -128 to 127.
- Range (Unsigned): 0 to 255.
- The signedness of
char
is implementation-defined; it can either be signed or unsigned by default. unsigned char
: Explicitly defines a character type that can only hold non-negative values (0 to 255).
Bool
Type:¶
bool
:- Introduced in the C99 standard.
- Size: Implementation-defined, often 1 byte.
- Values:
true
(1) andfalse
(0). - To use
bool
,#include <stdbool.h>
is required, which definesbool
as
a macro expanding to_Bool
, a built-in type introduced in C99.
Void Type:¶
void
: Represents the absence of type.
Used for functions that do not return a value or as a generic pointer void*
.
void
:- Does not represent any data type and thus does not have a size.
- Usage:
- As the return type of functions that do not return a value.
- As a generic pointer type (
void*
), which can point to any data type, but
cannot be dereferenced without casting to another type first.
Floats vs Doubles¶
Floats and Doubles both store floating point values.
Floats have a size of 4 bytes (or 32 bits).
Doubles have a size of 8 bytes (or 64 bits).
Floats | Doubles |
---|---|
4 bytes (32 bits) | 8 bytes (64 bits) |
7 decimal digit precision | 15 decimal digit precision |
possible precision errors with big numbers | won't get precision errors with big numbers |
Format specifier %f |
Format specifier %lf |
Precise Types¶
These types follow a naming convention that clearly indicates their size and whether they are signed or unsigned.
This is not in the traditional C types (int
, short
, long
), where the size can
vary, depending on the compiler and system architecture.
The syntax is different from traditional C types because these are
typedefs (type definitions) provided in the <stdint.h>
header
file, specifying exact widths.
Derived Data Types¶
-
Pointers:
- Used to store memory addresses.
- Their type is defined by the type of data they point to, e.g.,
int*
for a pointer to an integer.
-
Arrays:
- A collection of elements of the same type, stored contiguously in memory.
- E.g.,
int arr[10];
for an array of 10 integers.
-
Structs (
struct
):- A composite data type that groups variables of different types under a single name, e.g., structuring a student record with name, id, and GPA.
-
Unions (
union
):- Similar to structures, but members share the same memory location, useful for storing data that could be of multiple types.
-
Enumerations (
enum
):- Defines a type that can have one of a few predefined constants, improving code readability and maintainability.
Typedef¶
Declaring a type alias and/or custom struct type is done with the typedef
keyword.
Example of a custom struct type:
typedef struct this_is_optional {
int num_needed;
char **needed;
// Or:
/* int *needed[]; */
int spent;
} grocery_list_t;
Syntax broken down:
typedef struct this_is_optional
:- This declares a struct type.
- The
this_is_optional
is the name of the struct type.- This is optional, the actual name goes at the end of the block.
- The struct members are defined inside the braces
{ }
. - After the closing brace, we can define the name of the struct type.
- In this case,
grocery_list_t
. - The
_t
is a naming convention for custom types.
- In this case,
Fixed-Width Integer Types¶
These types allow for declaring integers of a specific size (width).
These types are useful anywhere that the precise size of an integer is critical for
the program, like embedded systems programming or cross-platform development.
Usage¶
To use these types, include the stdint.h
header file at the
beginning of your source file:
#include <stdint.h>
/* or, in C++ */
#include <cstdint> // C++ only.
Most of the time, we'll use the int32_t
type.
To use the unsigned variant of one of these types,
just prepend u
to the type name (e.g., uint32_t
).
The _t
syntax is a naming convention for types made with typedef
.
Signed Fixed-Width Integer Types¶
int8_t
,int16_t
,int32_t
,int64_t
:- These types represent signed integers of 8, 16, 32, and 64 bits, respectively.
- Ranges:
int8_t
: -128 to 127int16_t
: -32,768 to 32,767int32_t
: -2,147,483,648 to 2,147,483,647int64_t
: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
Unsigned Fixed-Width Integer Types¶
uint8_t
,uint16_t
,uint32_t
,uint64_t
:- These types represent unsigned integers of 8, 16, 32, and 64 bits, respectively.
- Ranges:
uint8_t
: 0 to 255uint16_t
: 0 to 65,535uint32_t
: 0 to 4,294,967,295uint64_t
: 0 to 18,446,744,073,709,551,615
Minimum-Width Integer Types¶
These are the smallest types that have at least the specified bit width.
- Minimum-width types (
int_least*
anduint_least*
):- Offer at least the specified bit width, potentially more, depending
on what's most efficient for the platform. - Useful for saving memory while ensuring a minimum capacity for data representation.
- Offer at least the specified bit width, potentially more, depending
Fastest Minimum-Width Integer Types¶
These are the fastest types with at least the specified bit width.
- Fastest minimum-width types (
int_fast*
anduint_fast*
):- These prioritize speed over exact size, providing the fastest type
with at least the specified width. - Ideal for performance-critical applications where operation speed is more
important than minimizing data size.
- These prioritize speed over exact size, providing the fastest type
Pointer-Sized Integer Types¶
These are signed and unsigned integer types capable of storing a pointer.
intptr_t
anduintptr_t
:- Capable of storing a pointer, thus aligning with the machine's address width.
- Essential for scenarios requiring the manipulation or arithmetic of pointers
and integers interchangeably. - Useful for integer-pointer conversions without loss of information.
Maximum-Width Integer Types¶
These are the largest signed and unsigned integers supported.
intmax_t
anduintmax_t
:- Represent the largest signed and unsigned integers supported by the implementation.
- Useful for operations that need to accommodate the widest range of integer values possible.
Best Practices for Fixed-Width Integer Types¶
- General-purpose programming
- Continue using Standard C types (
int
,unsigned int
, etc.) when the
exact size is not critical. - These are suitable for most purposes and are optimized for native architecture speed.
- Continue using Standard C types (
- Predictable, exact-size integers
- Fixed-width types should be used when precise control over integer size
and consistent behavior across platforms are necessary.
- Fixed-width types should be used when precise control over integer size