Every variable in C has a type, chosen when you declare it and fixed for its lifetime. The type decides three things: how many bytes the variable occupies, how those bytes are interpreted, and what operations make sense on it.
C's type list is short. The complexity is in the modifiers.
The Four Base Types
charholds one byte. It is used for single characters ('A'), for the bytes of a string, and sometimes as a tiny integer.intis the workhorse whole-number type. Loop counters, sizes, IDs.floatanddoublehold real numbers.doublehas roughly twice the precision.voidis the fourth type in a sense, but it means "no value" - you cannot declare avoidvariable, only use it as a function return type, an empty parameter list, or a typeless pointer.
Note the f suffix on 3.14f. Without it the literal is a double, and assigning it to a float loses precision silently. Some compilers warn about it.
Modifiers: short, long, unsigned
The base integer types come with modifiers that change the size or the sign.
Each integer type needs its own format specifier: %hd for short, %d for int, %ld for long, %lld for long long, %u for unsigned. Using the wrong one is undefined behavior, not a rounding error.
unsigned removes the sign bit and doubles the positive range. An unsigned int holds 0 to about 4.3 billion instead of -2.1 to +2.1 billion. It is the right choice for things that genuinely cannot be negative - sizes, counts of bytes, bit patterns - and a trap for anything that might be subtracted below zero.
signed is the default for int, short, long, and long long, so you almost never write it. The one place it matters is char: whether plain char is signed or unsigned is implementation-defined, so write signed char or unsigned char when the sign matters.
Sizes: What You Actually Get
The C standard specifies minimums, not exact sizes. On any modern 64-bit Linux, macOS, or Windows machine you will see this:
| Type | Typical size | Typical range |
|---|---|---|
char | 1 byte | -128 to 127 (or 0 to 255) |
short | 2 bytes | -32,768 to 32,767 |
int | 4 bytes | -2,147,483,648 to 2,147,483,647 |
long | 8 bytes (4 on Windows) | roughly ±9.2 quintillion |
long long | 8 bytes | roughly ±9.2 quintillion |
float | 4 bytes | ~7 significant digits |
double | 8 bytes | ~15 significant digits |
long double | 16 bytes (varies) | more than double |
The long row is the one that catches people: it is 8 bytes on Linux and macOS, and 4 bytes on 64-bit Windows. Code that assumes long can hold a 64-bit value is not portable. Use long long, or the exact-width types from stdint.h (int32_t, uint64_t) when the size is part of the requirement.
sizeof: Ask the Compiler
Never guess a size - measure it:
sizeof is an operator, not a function, and it is evaluated at compile time. It yields a value of type size_t, which prints with %zu.
sizeof(char) is guaranteed to be exactly 1 - that is the definition of a byte in C. Everything else is measured relative to it.
limits.h and float.h
The exact ranges for your compiler are available as named constants:
These are the values to compare against when you need to know whether an operation will overflow. Checking if (a > INT_MAX - b) before computing a + b is how you detect overflow before it happens - which matters, because you cannot detect signed overflow after.
Integer Overflow
What happens when a value exceeds its type's range depends entirely on the sign.
Unsigned overflow is defined: the value wraps modulo 2^N.
That second case is a real source of bugs. A loop like for (unsigned i = n - 1; i >= 0; i--) never ends, because an unsigned value is always >= 0.
Signed overflow is undefined behavior. Not "wraps around" - undefined. The compiler is allowed to assume it never happens and optimize on that basis, which means an overflow check written after the fact can be deleted:
int sum = a + b;
if (sum < a) { /* the compiler may remove this entirely */ }
Check before, using the limits:
if (b > 0 && a > INT_MAX - b) {
/* a + b would overflow - handle it */
}
Floating-Point Precision
float and double store numbers in binary, and most decimal fractions have no exact binary form - the same way 1/3 has no exact decimal form.
The rule that follows: never compare floating-point values with ==. Compare the absolute difference against a small tolerance instead.
And never use floating-point for money. Store cents as an integer; a long long of cents is exact where a double of dollars is not.
Choosing a Type
A short decision list that covers most code:
- Whole numbers:
intunless you have a reason. It is the type the CPU handles most efficiently and the type that all the arithmetic rules are built around. - Anything bigger than 2 billion:
long long, orint64_tfromstdint.h. - Sizes, lengths, array indices from
sizeoforstrlen:size_t. It is unsigned and guaranteed large enough for any object. - Decimals:
double. Usefloatonly to halve memory in large arrays or on embedded hardware without a double-precision unit. - Single characters and raw bytes:
charfor text,unsigned charfor binary data. - True/false:
boolfromstdbool.h- see booleans in C. - Exact bit widths (file formats, network protocols, hardware registers):
stdint.h-uint8_t,int16_t,uint32_t, and so on.
Mixing Types
When you combine two different types in one expression, C converts them behind your back before doing the arithmetic. That is usually helpful and occasionally disastrous:
The first is integer division: both operands are int, so the result is an int and the fraction is discarded. The third is worse - comparing a signed value with an unsigned one converts the signed one to unsigned, turning -1 into a huge positive number.
Those conversion rules, and how to take control of them with explicit casts, are the subject of type casting in C.
Frequently Asked Questions
What are the basic data types in C?
Four base types: char for single characters and bytes, int for whole numbers, float and double for decimals. Modifiers change their size and sign - short, long, long long, signed, and unsigned - producing the full set.
How many bytes is an int in C?
Almost always 4 bytes (32 bits) on modern desktop and server systems, giving a range of about -2.1 billion to 2.1 billion. The standard only guarantees at least 2 bytes, and 16-bit microcontrollers do use 2. Use sizeof(int) if you need to know for certain.
What is the difference between float and double in C?
float is 4 bytes with about 7 significant decimal digits; double is 8 bytes with about 15. Double is the default for floating-point literals and math functions, and unless you are storing millions of values or targeting an embedded chip, double is the right choice.
What happens when an int overflows in C?
For a signed int, overflow is undefined behavior - the compiler may wrap, saturate, or optimize the check away entirely. For an unsigned int it is fully defined: the value wraps around modulo 2^N, so UINT_MAX + 1 is 0. Never rely on signed overflow.