Sizes, Limits & Overflow
Every C type has a fixed size in memory, and that size limits the values it can hold. An int that counts cricket runs will never reach its limit, but an int that adds up a company's yearly sales in paise easily can. When a value goes past the limit, C does not stop with an error. It silently produces a wrong answer, or worse.
In this lesson you will measure types with sizeof, read their exact limits, use types with guaranteed sizes, and learn how to detect overflow before it causes damage.
Measuring sizes with sizeof
The sizeof operator gives the size of a type or a variable in bytes (one byte is 8 bits on every normal system). Its result has the type size_t, an unsigned whole-number type, and you print it with %zu.
#include <stdio.h>
int main(void)
{
printf("char: %zu\n", sizeof(char));
printf("short: %zu\n", sizeof(short));
printf("int: %zu\n", sizeof(int));
printf("long: %zu\n", sizeof(long));
printf("long long: %zu\n", sizeof(long long));
printf("float: %zu\n", sizeof(float));
printf("double: %zu\n", sizeof(double));
printf("bool: %zu\n", sizeof(bool));
int marks = 90;
printf("marks: %zu\n", sizeof marks);
return 0;
}char: 1 short: 2 int: 4 long: 8 long long: 8 float: 4 double: 8 bool: 1 marks: 4
This output is from 64-bit Linux, and macOS gives the same numbers. On 64-bit Windows, long is only 4 bytes, so the same program prints long: 4. This is the most common size difference between systems, and the reason careful programmers avoid long when they need a definite size. With a type name, sizeof needs brackets; with a variable name, the brackets are optional.
Limits from limits.h
The header limits.h defines the smallest and largest value of each whole-number type as named constants. The header float.h does the same for floating-point types.
#include <float.h>
#include <limits.h>
#include <stdio.h>
int main(void)
{
printf("Bits in a char: %d\n", CHAR_BIT);
printf("int: %d to %d\n", INT_MIN, INT_MAX);
printf("unsigned: 0 to %u\n", UINT_MAX);
printf("long long: %lld to %lld\n", LLONG_MIN, LLONG_MAX);
printf("double keeps about %d significant digits\n", DBL_DIG);
return 0;
}Bits in a char: 8 int: -2147483648 to 2147483647 unsigned: 0 to 4294967295 long long: -9223372036854775808 to 9223372036854775807 double keeps about 15 significant digits
The pattern comes from binary. A signed type with N bits holds values from -2^(N-1) up to 2^(N-1) - 1, where ^ here means "to the power of". For a 32-bit int, that is about ±2.1 billion. The unsigned version holds 0 up to 2^N - 1, about 4.2 billion. C23 requires signed integers to use two's complement, the binary format every modern processor already uses, so these ranges are now the same everywhere for a given size.
Fixed-width types: stdint.h
When a value must have an exact size, for example in a file format, a network message or code for a microcontroller, use the types from stdint.h. The name says the size: int8_t, int16_t, int32_t and int64_t are signed, and uint8_t to uint64_t are unsigned. The header inttypes.h includes stdint.h and adds the matching printf macros.
#include <inttypes.h>
#include <stdio.h>
int main(void)
{
int64_t distance_mm = 3000000000; // 3,000 km in millimetres
uint8_t brightness = 200; // a pixel value, 0 to 255
int16_t temperature = -12; // a winter night in Leh
printf("Distance: %" PRId64 " mm\n", distance_mm);
printf("Brightness: %" PRIu8 "\n", brightness);
printf("Temperature: %" PRId16 " C\n", temperature);
printf("int32_t uses %zu bytes\n", sizeof(int32_t));
return 0;
}Distance: 3000000000 mm Brightness: 200 Temperature: -12 C int32_t uses 4 bytes
PRId64 is a macro that turns into the right letters, such as "ld" or "lld", for the current system. C joins string literals that sit next to each other, so "Distance: %" PRId64 " mm\n" becomes one complete format string. It looks odd at first, but it prints a 64-bit value correctly on every platform.
Overflow: when a value does not fit
Overflow happens when the result of a calculation is too big or too small for its type. What happens next depends on whether the type is signed or unsigned.
Unsigned types wrap around. The value is reduced modulo 2^N (256 for an 8-bit type), like a car's odometer rolling over from 99999 to 00000. This behaviour is fully defined and sometimes useful.
#include <limits.h>
#include <stdio.h>
int main(void)
{
unsigned char level = 250;
level = level + 10; // 260 does not fit in 0..255
printf("level = %d\n", level);
unsigned int counter = 0;
counter = counter - 1;
printf("0 - 1 as unsigned = %u\n", counter);
printf("UINT_MAX = %u\n", UINT_MAX);
return 0;
}level = 4 0 - 1 as unsigned = 4294967295 UINT_MAX = 4294967295
260 is 4 more than 256, so level wraps to 4. Going below zero wraps to the largest value.
Signed overflow is undefined behaviour. If an int calculation goes past INT_MAX, the C standard says nothing about the result. The program might print a negative number, or the compiler's optimiser might remove a safety check that it assumed could never fail. You must make sure signed overflow never happens. A common trap is multiplying two ordinary numbers: 50000 * 50000 is 2.5 billion, which does not fit in an int. Write 50000LL * 50000 so the calculation is done in long long.
Checking for overflow with ckd_add
#include <stdckdint.h>
#include <stdio.h>
int main(void)
{
int a = 1500000000;
int b = 900000000;
int sum;
if (ckd_add(&sum, a, b)) {
printf("%d + %d overflows an int\n", a, b);
} else {
printf("Sum: %d\n", sum);
}
if (ckd_add(&sum, 1200, 99)) {
printf("1200 + 99 overflows an int\n");
} else {
printf("Sum: %d\n", sum);
}
return 0;
}1500000000 + 900000000 overflows an int Sum: 1299
The & before sum passes the variable's memory address so that ckd_add can store the result there; you will learn this in Pointers, NULL & nullptr. stdckdint.h is available from GCC 14 and Clang 18. With older compilers, check before you add: for positive b, the sum overflows exactly when a > INT_MAX - b. That test itself can never overflow.
Choosing the right type
| Kind of value | Good type |
|---|---|
| Counts, marks, ages, loop counters | int |
Array lengths, memory sizes, results of sizeof | size_t |
| Large totals, money in paise | long long or int64_t |
| Raw bytes, pixel values | uint8_t or unsigned char |
| Exact sizes in files and network data | int32_t, uint16_t and friends |
| Measurements with decimals | double |
Common mistakes
- Printing
sizeofwith%d. Its type issize_t; use%zu. - Assuming
longis 8 bytes. It is 4 bytes on Windows. Uselong longorint64_twhen you need 64 bits. - Mixing signed and unsigned in a comparison. In
-1 < 10u, the -1 is converted to unsigned and becomes 4294967295, so the result is false.-Wextrawarns about such comparisons. - Counting down with an unsigned variable.
for (unsigned i = 5; i >= 0; i--)never ends, because an unsigned value is always 0 or more.
Key points
sizeofgives sizes in bytes as asize_t; print it with%zu.- Sizes vary by system:
longis 8 bytes on 64-bit Linux and macOS but 4 bytes on Windows. limits.handfloat.hgive exact limits such asINT_MAXandDBL_DIG.stdint.htypes likeint32_tanduint8_thave guaranteed sizes; print them with theinttypes.hmacros.- Unsigned overflow wraps around; signed overflow is undefined behaviour and must be prevented.
- C23
ckd_add,ckd_subandckd_muldetect overflow safely.
Exercise
A cinema chain sold 2,000,000 tickets at Rs 1,150 each and stores money in paise. Write a program that multiplies the tickets by the price in paise (115000) using ckd_mul with an int result, and prints a warning if it overflows. Then do the same calculation with an int64_t result and print the total.
Show solution
#include <inttypes.h>
#include <stdckdint.h>
#include <stdio.h>
int main(void)
{
int tickets = 2000000;
int price_paise = 115000;
int small_total;
if (ckd_mul(&small_total, tickets, price_paise)) {
printf("Too big for an int!\n");
}
int64_t total;
if (ckd_mul(&total, tickets, price_paise)) {
printf("Too big even for int64_t!\n");
} else {
printf("Total: %" PRId64 " paise\n", total);
}
return 0;
}Too big for an int! Total: 230000000000 paise
The product is 230 billion paise (Rs 230 crore), far above INT_MAX. ckd_mul computes the exact result and checks it against the type of the result variable, so the second call succeeds with int64_t.
Quick Quiz
1. What does sizeof(int) usually give on a 64-bit computer?
int is 4 bytes on all common 64-bit systems.
2. What happens when an unsigned int holding 0 has 1 subtracted from it?
Unsigned arithmetic is defined to wrap around, so 0 - 1 becomes UINT_MAX.
3. Which statement about signed integer overflow is correct?
The C standard gives no meaning to signed overflow; compilers may assume it never happens.
4. Which format specifier prints a size_t?
%zu is the correct specifier for size_t, the type of sizeof.
Learn C with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
