Skip to content

C Data Types

In C, data types define the kind of data a variable can hold and the operations that can be performed on it. They specify the amount of memory allocated for a variable and how the stored bit pattern is interpreted.

C data types can be broadly categorized as follows:

CategoryDescription
1Fundamental Types: These are the basic building blocks for data manipulation. They include:
a) Integer types (like int, char, short, long, long long, including unsigned variants and _Bool)
b) Floating-point types (float, double, long double)
2Enumerated Types (enum): User-defined types where variables can only take values from a predefined set of integer constants, improving code readability and maintainability.
3The void Type: Represents the absence of type. Used primarily for functions that return nothing, accept no arguments, or for generic pointers (void *).
4Derived Types: Types constructed from fundamental types or other derived types. These include:
a) Pointer types (*)
b) Array types ([])
c) Structure types (struct)
d) Union types (union)
e) Function types

Arrays and structures are often grouped as aggregate types because they can hold multiple values. Function types describe the return type and parameters of a function. We’ll focus on fundamental types here; others are covered later.

Integer types store whole numbers. The C standard guarantees minimum ranges for these types, but their exact size (in bytes) and precise range can vary depending on the compiler and architecture (e.g., 32-bit vs 64-bit systems).

Common integer types and their characteristics (minimum ranges mandated by the C standard):

TypeTypical SizeMinimum Value Range (Standard Guarantee)Common Use
charTypically 1 byteUsually -128 to 127 (signed) or 0 to 255 (unsigned), implementation-definedSmallest addressable unit, often used for characters (ASCII) or small integers.
signed charTypically 1 byte-127 to 127Explicitly signed character type.
unsigned charTypically 1 byte0 to 255Explicitly unsigned character type, useful for raw byte manipulation.
short (short int)At least 2 bytes-32,767 to 32,767For smaller integer values.
unsigned shortAt least 2 bytes0 to 65,535For non-negative smaller integers.
intTypically matches the ‘natural’ word size of the machine (often 4 bytes on modern systems), at least 2 bytes-32,767 to 32,767 (minimum guaranteed, often much larger like ±2 billion)Default integer type, commonly used.
unsigned intSame size as int0 to 65,535 (minimum guaranteed, often up to 4 billion)For non-negative integers, bit manipulation.
long (long int)At least 4 bytes-2,147,483,647 to 2,147,483,647For larger integer values.
unsigned longSame size as long0 to 4,294,967,295For large non-negative integers.
long long (long long int) (C99+)At least 8 bytes±9 x 10^18 (approx)For very large integer values.
unsigned long long (C99+)Same size as long long0 to 1.8 x 10^19 (approx)For very large non-negative integers.
_Bool (C99+)Typically 1 byte0 or 1For boolean values (true/false). Include <stdbool.h> for bool, true, false macros.

Fixed-Width Integers (C99+): For situations requiring predictable sizes across platforms, use types defined in <stdint.h>, such as int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t. These are highly recommended for portable code dealing with binary data formats or hardware interfaces.

To get the exact size of a type or variable on your specific system, use the sizeof operator. It returns the size in bytes as a size_t value (an unsigned integer type). Use the %zu format specifier to print size_t values.

// Include standard I/O and limits headers
#include <stdio.h>
#include <limits.h> // Provides constants for integer type ranges (e.g., INT_MIN, INT_MAX)
#include <stdint.h> // Provides fixed-width integer types
int main(void) { // Use void to indicate no arguments
printf("Size of int on this system: %zu bytes\n", sizeof(int));
printf("Size of long long on this system: %zu bytes\n", sizeof(long long));
printf("Size of int32_t: %zu bytes\n", sizeof(int32_t)); // Guaranteed to be 4 bytes (32 bits)
// Example using limits
printf("Minimum value for int: %d\n", INT_MIN);
printf("Maximum value for int: %d\n", INT_MAX);
return 0;
}

Compile and run this code. The output for sizeof(int) might be 4 on most modern systems, but could differ.

Example Output (typical 64-bit Linux):

Size of int on this system: 4 bytes
Size of long long on this system: 8 bytes
Size of int32_t: 4 bytes
Minimum value for int: -2147483648
Maximum value for int: 2147483647

Floating-point types represent real numbers (numbers with a fractional part). They typically follow the IEEE 754 standard for representation.

TypeTypical SizeApproximate RangeApproximate PrecisionCommon Use
float4 bytesApprox ±1.2E-38 to ±3.4E+38Approx 6-7 decimal digitsSingle-precision floating-point. Use when memory is tight and high precision isn’t critical.
double8 bytesApprox ±2.3E-308 to ±1.7E+308Approx 15-16 decimal digitsDouble-precision floating-point. The default for floating-point literals and generally preferred for calculations.
long doubleOften 10 or 16 bytes (implementation-defined)Varies, typically wider range than doubleVaries, typically higher precision than doubleExtended-precision floating-point. Use when maximum precision is needed.

The <float.h> header file defines macros for floating-point characteristics, like minimum/maximum values (FLT_MIN, FLT_MAX, DBL_MIN, DBL_MAX) and precision (FLT_DIG, DBL_DIG).

#include <stdio.h>
#include <float.h> // Provides constants for float type limits
int main(void) {
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Smallest positive float value: %E\n", FLT_MIN); // Smallest normalized positive value
printf("Largest positive float value: %E\n", FLT_MAX);
printf("Float precision (decimal digits): %d\n", FLT_DIG);
printf("\nSize of double: %zu bytes\n", sizeof(double));
printf("Double precision (decimal digits): %d\n", DBL_DIG);
return 0;
}

Example Output (typical system):

Size of float: 4 bytes
Smallest positive float value: 1.175494E-38
Largest positive float value: 3.402823E+38
Float precision (decimal digits): 6
Size of double: 8 bytes
Double precision (decimal digits): 15

The void type has a special meaning: it indicates the absence of a type or value. It’s used in three main contexts:

ContextDescription and Example
1Function Return Type: void func(...) declares a function that does not return any value. Example: void print_hello(void);
2Function Arguments: int func(void) declares a function that takes no arguments. This is the standard way in C to specify an empty parameter list (required since C99, though many compilers accept () for compatibility). Example: int getchar(void);
3Generic Pointers (void *): A pointer of type void * can hold the address of any object type, but not a function pointer. It’s a generic pointer used for manipulating raw memory or passing addresses of unknown type, like with malloc and free. You must explicitly cast a void * to another pointer type before dereferencing it. Example: void *malloc(size_t size); returns a generic pointer to allocated memory.

Understanding void, especially void *, becomes clearer when learning about functions and pointers in later chapters.