Fundamental Data Types
Introduction
Data types are the foundation of any programming language, defining the kind of values that can be stored and manipulated in a program. In C, data types determine the size and layout of variables in memory, the range of values they can hold, and the operations that can be performed on them.
Understanding C’s fundamental data types is crucial for writing efficient, correct, and portable code. This chapter will explore all the basic data types in C, from the smallest char to the largest integer and floating-point types.
Basic Data Types in C
C provides several fundamental data types that can be categorized into:
- Integer Types: For storing whole numbers
- Floating-Point Types: For storing real numbers with decimal points
- Character Types: For storing single characters
- Boolean Types: For storing true/false values (C99+)
Integer Types
Integer types are used to store whole numbers (positive, negative, or zero) without fractional components.
Basic Integer Types
| Type | Size (typical) | Range (typical) | Description |
|---|---|---|---|
char |
1 byte | -128 to 127 | Character or small integer |
short |
2 bytes | -32,768 to 32,767 | Short integer |
int |
4 bytes | -2,147,483,648 to 2,147,483,647 | Standard integer |
long |
4 or 8 bytes | Varies | Long integer |
long long |
8 bytes | -(2^63) to (2^63)-1 | Long long integer (C99) |
Unsigned Integer Types
Unsigned integers can only store non-negative values (zero or positive) but have a larger positive range:
| Type | Size (typical) | Range (typical) | Description |
|---|---|---|---|
unsigned char |
1 byte | 0 to 255 | Unsigned character |
unsigned short |
2 bytes | 0 to 65,535 | Unsigned short integer |
unsigned int |
4 bytes | 0 to 4,294,967,295 | Unsigned standard integer |
unsigned long |
4 or 8 bytes | Varies | Unsigned long integer |
unsigned long long |
8 bytes | 0 to (2^64)-1 | Unsigned long long integer (C99) |
Size and Limits
The actual size of integer types can vary depending on the system architecture and compiler. To determine the exact sizes and limits on your system, you can use the limits.h header:
#include <stdio.h>
#include <limits.h>
int main() {
printf("Size of char: %zu bytes\n", sizeof(char));
printf("Size of short: %zu bytes\n", sizeof(short));
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of long: %zu bytes\n", sizeof(long));
printf("Size of long long: %zu bytes\n", sizeof(long long));
printf("\nCHAR_MIN: %d\n", CHAR_MIN);
printf("CHAR_MAX: %d\n", CHAR_MAX);
printf("SHRT_MIN: %d\n", SHRT_MIN);
printf("SHRT_MAX: %d\n", SHRT_MAX);
printf("INT_MIN: %d\n", INT_MIN);
printf("INT_MAX: %d\n", INT_MAX);
printf("LONG_MIN: %ld\n", LONG_MIN);
printf("LONG_MAX: %ld\n", LONG_MAX);
printf("LLONG_MIN: %lld\n", LLONG_MIN);
printf("LLONG_MAX: %lld\n", LLONG_MAX);
return 0;
}Floating-Point Types
Floating-point types are used to store real numbers with fractional components.
Basic Floating-Point Types
| Type | Size (typical) | Precision | Range | Description |
|---|---|---|---|---|
float |
4 bytes | 6-7 decimal digits | ±3.4×10^38 | Single precision |
double |
8 bytes | 15-16 decimal digits | ±1.7×10^308 | Double precision |
long double |
10-16 bytes | Extended precision | Extended range | Extended precision |
Floating-Point Limits
To determine the exact characteristics of floating-point types on your system, use the float.h header:
#include <stdio.h>
#include <float.h>
int main() {
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Size of double: %zu bytes\n", sizeof(double));
printf("Size of long double: %zu bytes\n", sizeof(long double));
printf("\nFLT_DIG: %d\n", FLT_DIG);
printf("DBL_DIG: %d\n", DBL_DIG);
printf("LDBL_DIG: %d\n", LDBL_DIG);
printf("FLT_MIN: %e\n", FLT_MIN);
printf("FLT_MAX: %e\n", FLT_MAX);
printf("DBL_MIN: %e\n", DBL_MIN);
printf("DBL_MAX: %e\n", DBL_MAX);
return 0;
}Character Types
The char type is used to store single characters, but it’s fundamentally an integer type that stores ASCII values.
Basic Character Type
#include <stdio.h>
int main() {
char letter = 'A';
char number = 65; // ASCII value of 'A'
printf("Letter: %c\n", letter);
printf("Number: %d\n", letter);
printf("Same as: %c\n", number);
return 0;
}Signed vs Unsigned Char
The char type can be either signed or unsigned, depending on the implementation:
#include <stdio.h>
int main() {
signed char s_char = -1;
unsigned char u_char = 255;
printf("Signed char: %d\n", s_char);
printf("Unsigned char: %u\n", u_char);
return 0;
}Boolean Type (C99+)
C99 introduced the _Bool type for boolean values, and C99+ provides the bool type through the stdbool.h header.
Using _Bool
#include <stdio.h>
int main() {
_Bool is_complete = 1;
_Bool is_valid = 0;
printf("is_complete: %d\n", is_complete);
printf("is_valid: %d\n", is_valid);
return 0;
}Using stdbool.h
#include <stdio.h>
#include <stdbool.h>
int main() {
bool is_complete = true;
bool is_valid = false;
printf("is_complete: %d\n", is_complete);
printf("is_valid: %d\n", is_valid);
if (is_complete) {
printf("Task is complete!\n");
}
return 0;
}Fixed-Width Integer Types (C99+)
To ensure portability across different systems, C99 introduced fixed-width integer types in the stdint.h header:
Exact Width Types
| Type | Bits | Range |
|---|---|---|
int8_t |
8 | -128 to 127 |
int16_t |
16 | -32,768 to 32,767 |
int32_t |
32 | -2,147,483,648 to 2,147,483,647 |
int64_t |
64 | -(2^63) to (2^63)-1 |
uint8_t |
8 | 0 to 255 |
uint16_t |
16 | 0 to 65,535 |
uint32_t |
32 | 0 to 4,294,967,295 |
uint64_t |
64 | 0 to (2^64)-1 |
Minimum Width Types
| Type | Minimum Bits |
|---|---|
int_least8_t |
8 |
int_least16_t |
16 |
int_least32_t |
32 |
int_least64_t |
64 |
Fastest Minimum Width Types
| Type | Minimum Bits |
|---|---|
int_fast8_t |
8 |
int_fast16_t |
16 |
int_fast32_t |
32 |
int_fast64_t |
64 |
Example Usage
#include <stdio.h>
#include <stdint.h>
int main() {
int32_t id = 123456789;
uint16_t age = 25;
int8_t score = -5;
printf("ID: %" PRId32 "\n", id);
printf("Age: %" PRIu16 "\n", age);
printf("Score: %" PRId8 "\n", score);
return 0;
}Size-Specific Integer Types
The stdint.h header also provides types that match the size of pointers and the maximum values:
Pointer-Sized Types
| Type | Description |
|---|---|
intptr_t |
Signed integer type capable of holding a pointer |
uintptr_t |
Unsigned integer type capable of holding a pointer |
Maximum Value Types
| Type | Description |
|---|---|
intmax_t |
Maximum width signed integer type |
uintmax_t |
Maximum width unsigned integer type |
Type Specifiers and Qualifiers
C provides several type specifiers and qualifiers to modify data types:
Type Specifiers
| Specifier | Description |
|---|---|
signed |
Explicitly signed type |
unsigned |
Unsigned type |
long |
Long integer or double |
short |
Short integer |
long long |
Long long integer (C99) |
Type Qualifiers
| Qualifier | Description |
|---|---|
const |
Constant value (cannot be modified) |
volatile |
Value may change unexpectedly |
restrict |
Pointer with no aliasing (C99) |
Examples
#include <stdio.h>
int main() {
const int max_size = 100;
volatile int sensor_value;
long long big_number = 123456789012345LL;
unsigned short port = 8080;
printf("Max size: %d\n", max_size);
printf("Big number: %lld\n", big_number);
printf("Port: %hu\n", port);
return 0;
}sizeof Operator
The sizeof operator returns the size (in bytes) of a data type or variable:
#include <stdio.h>
int main() {
printf("Size of char: %zu bytes\n", sizeof(char));
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Size of double: %zu bytes\n", sizeof(double));
printf("Size of long long: %zu bytes\n", sizeof(long long));
int number = 42;
printf("Size of variable 'number': %zu bytes\n", sizeof(number));
printf("Size of int: %zu bytes\n", sizeof number); // Parentheses optional for variables
return 0;
}Type Conversion and Promotion
C automatically performs type conversions in expressions:
Integer Promotion
#include <stdio.h>
int main() {
char c = 'A';
int i = c; // char is promoted to int
printf("Character: %c\n", c);
printf("ASCII value: %d\n", i);
return 0;
}Usual Arithmetic Conversions
#include <stdio.h>
int main() {
int i = 10;
float f = 3.14f;
double d = i + f; // int is converted to float, then to double
printf("Result: %f\n", d);
return 0;
}Practical Examples
Memory Calculator
#include <stdio.h>
#include <stdint.h>
int main() {
printf("=== Memory Requirements Calculator ===\n");
size_t char_size = sizeof(char);
size_t int_size = sizeof(int);
size_t float_size = sizeof(float);
size_t double_size = sizeof(double);
size_t pointer_size = sizeof(void*);
printf("char: %zu byte(s)\n", char_size);
printf("int: %zu byte(s)\n", int_size);
printf("float: %zu byte(s)\n", float_size);
printf("double: %zu byte(s)\n", double_size);
printf("pointer: %zu byte(s)\n", pointer_size);
// Calculate memory for arrays
int array_size = 1000;
printf("\nMemory for %d elements:\n", array_size);
printf("char array: %zu bytes\n", array_size * sizeof(char));
printf("int array: %zu bytes\n", array_size * sizeof(int));
printf("float array: %zu bytes\n", array_size * sizeof(float));
printf("double array: %zu bytes\n", array_size * sizeof(double));
return 0;
}Data Type Limits Explorer
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main() {
printf("=== Data Type Limits Explorer ===\n\n");
// Integer limits
printf("Integer Limits:\n");
printf("CHAR_MIN: %d\n", CHAR_MIN);
printf("CHAR_MAX: %d\n", CHAR_MAX);
printf("SHRT_MIN: %d\n", SHRT_MIN);
printf("SHRT_MAX: %d\n", SHRT_MAX);
printf("INT_MIN: %d\n", INT_MIN);
printf("INT_MAX: %d\n", INT_MAX);
printf("LONG_MIN: %ld\n", LONG_MIN);
printf("LONG_MAX: %ld\n", LONG_MAX);
printf("LLONG_MIN: %lld\n", LLONG_MIN);
printf("LLONG_MAX: %lld\n", LLONG_MAX);
// Unsigned integer limits
printf("\nUnsigned Integer Limits:\n");
printf("UCHAR_MAX: %u\n", UCHAR_MAX);
printf("USHRT_MAX: %u\n", USHRT_MAX);
printf("UINT_MAX: %u\n", UINT_MAX);
printf("ULONG_MAX: %lu\n", ULONG_MAX);
printf("ULLONG_MAX: %llu\n", ULLONG_MAX);
// Floating-point limits
printf("\nFloating-Point Limits:\n");
printf("FLT_MIN: %e\n", FLT_MIN);
printf("FLT_MAX: %e\n", FLT_MAX);
printf("DBL_MIN: %e\n", DBL_MIN);
printf("DBL_MAX: %e\n", DBL_MAX);
return 0;
}Best Practices
1. Use Fixed-Width Types for Portability
#include <stdint.h>
// Use int32_t instead of int when you need exactly 32 bits
int32_t user_id = 12345;2. Check System Limits
#include <limits.h>
if (value > INT_MAX) {
printf("Value exceeds integer maximum!\n");
}3. Use Appropriate Format Specifiers
#include <inttypes.h>
int32_t number = 123456789;
printf("Number: %" PRId32 "\n", number);4. Be Aware of Overflow
#include <limits.h>
int result;
if (a > INT_MAX - b) {
printf("Addition would overflow!\n");
} else {
result = a + b;
}Summary
In this chapter, you’ve learned about:
- Integer Types: char, short, int, long, long long and their unsigned variants
- Floating-Point Types: float, double, long double
- Character Types: char and its signed/unsigned variations
- Boolean Types: _Bool and bool (with stdbool.h)
- Fixed-Width Types: int8_t, int16_t, int32_t, int64_t and their unsigned counterparts
- Type Specifiers and Qualifiers: signed, unsigned, const, volatile, restrict
- sizeof Operator: Determining memory requirements
- Type Conversion: Automatic promotions and conversions
Understanding these fundamental data types is essential for writing efficient C programs. In the next chapter, we’ll explore variables and constants in detail, showing you how to declare, initialize, and use these data types effectively in your programs.