C / TYPES AND REPRESENTATION
int, its ranges and why they vary by platform
Work out the exact range of int on any target, explain why it differs between platforms, and write range checks that never overflow themselves.
What you will learn
- Read the real bounds from INT_MAX and INT_MIN in <limits.h>, never from a literal
- Derive int's range as -2^(N-1)..2^(N-1)-1 with N = sizeof(int) * CHAR_BIT
- Write overflow checks as a > INT_MAX - b so the risky addition never executes
- Explain int staying 32 bits on 64-bit systems as an ABI decision, not a CPU limit
Understanding int, its ranges and why they vary by platform
int is a signed integer type whose width the language deliberately leaves to the implementation. The standard requires only that it hold every value from -32767 to +32767, that it be no narrower than short, and no wider than long. The intent behind the type is the natural size suggested by the architecture: the signed integer the target CPU loads, adds and compares most comfortably. Writing int is a request for the machine's comfortable integer, not a promise of exactly 32 bits.
The concrete range follows from the width. With N = sizeof(int) * CHAR_BIT bits, a two's-complement int covers -2^(N-1) through 2^(N-1)-1, so 16 bits gives -32768..32767 and 32 bits gives -2147483648..2147483647. Small microcontroller toolchains such as avr-gcc and msp430-gcc use a 16-bit int, while desktop, server and phone targets use 32. The surprise is that int stayed at 32 when platforms moved to 64-bit registers: LP64 (Linux, macOS) widened long and pointers, LLP64 (Windows) widened only long long and pointers, and both left int alone so existing source, struct layouts and binary interfaces kept working, which means the width is pinned by the ABI rather than by what the hardware can do.
Because the bound is a property of the platform, the portable way to name it is INT_MAX and INT_MIN from <limits.h>, which each implementation defines to whatever is true for its target. Note the asymmetry: on two's-complement hardware INT_MIN is -INT_MAX - 1, so there is one more negative value than positive, and -INT_MIN has no int representation. Crossing either bound in signed arithmetic is undefined behaviour, not wraparound, and an optimizer may assume it never happens, so a range check has to be arranged to avoid the overflow instead of inspecting the result afterwards.
<stdio.h>
<limits.h>
int main(void)
{
printf("CHAR_BIT = %d\n", CHAR_BIT);
printf("sizeof(int) = %zu bytes (%zu bits)\n",
sizeof(int), sizeof(int) * CHAR_BIT);
printf("INT_MAX = %d\n", INT_MAX);
printf("INT_MIN = %d\n", INT_MIN);
printf("INT_MIN + INT_MAX = %d\n", INT_MIN + INT_MAX);
printf("standard floor = %d .. %d\n", -32767, 32767);
return 0;
}
int has a guaranteed minimum range rather than a fixed width, so its actual limits come from the target's ABI and must be read from <limits.h>.
Worked examples
Checking a sum against the bounds
Tests whether an addition fits in an int without ever performing the overflowing addition.
<stdio.h>
<limits.h>
/* true if a + b stays inside int's range */
static int can_add(int a, int b)
{
if (b > 0 && a > INT_MAX - b) return 0;
if (b < 0 && a < INT_MIN - b) return 0;
return 1;
}
int main(void)
{
printf("(INT_MAX - 10) + 20 fits? %d\n", can_add(INT_MAX - 10, 20));
printf("(INT_MAX - 10) + 5 fits? %d\n", can_add(INT_MAX - 10, 5));
printf("INT_MIN + (-1) fits? %d\n", can_add(INT_MIN, -1));
return 0;
}
Example explained
Line 1a > INT_MAX - b is the rearrangement of a + b > INT_MAX in which every intermediate value stays inside int.
Line 2The mirror test a < INT_MIN - b guards the negative end, which reaches one value further out than the positive end.
Line 3can_add(INT_MIN, -1) is false because INT_MIN - (-1) is INT_MIN + 1, and INT_MIN sits below that.
Line 4No literal bound appears anywhere in can_add, so it is equally correct where INT_MAX is 32767.
Which int does this platform have?
Selects a message at compile time from INT_MAX and shows the bit pattern that bound corresponds to.
<stdio.h>
<limits.h>
int main(void)
{
puts("int is 16 bits here (typical of small microcontroller targets)");
puts("int is 32 bits here (typical of desktop, server and phone ABIs)");
puts("int is wider than 32 bits here");
printf("value bits: %d, plus 1 sign bit\n",
(int)(sizeof(int) * CHAR_BIT - 1));
printf("INT_MAX bit pattern: %#x\n", (unsigned)INT_MAX);
return 0;
}
Example explained
Line 1#if can compare INT_MAX because the <limits.h> macros expand to integer constant expressions the preprocessor evaluates itself.
Line 2sizeof is not available to the preprocessor, so the bit count is computed at run time and cast to int for %d.
Line 30x7fffffff is INT_MAX in binary: the sign bit clear and all 31 value bits set.
Widen before you add, not after
Computes a value just past INT_MAX by promoting the operand first, then asks whether the result would fit back in an int.
<stdio.h>
<limits.h>
int main(void)
{
int n = INT_MAX;
long long wide = (long long)n + 1; /* widen first, then add */
printf("n = %d\n", n);
printf("(long long)n + 1 = %lld\n", wide);
printf("fits back into int? %s\n",
(wide >= INT_MIN && wide <= INT_MAX) ? "yes" : "no");
return 0;
}
Example explained
Line 1The cast applies to n before the addition, so the sum is formed in long long and never approaches int's ceiling.
Line 2Writing (long long)(n + 1) instead would overflow the int addition first and then widen an already undefined result.
Line 3Comparing wide against INT_MIN and INT_MAX is how you decide, at run time, whether a wide value may be stored in an int.
Important notes
The values shown come from a target where int is 32 bits and CHAR_BIT is 8; on a 16-bit-int target the same source prints 32767 and -32768, which is exactly why it prints the macros instead of literals.
C23 requires two's complement, so INT_MIN is -INT_MAX - 1 there, but C17 and earlier also allowed INT_MIN == -INT_MAX, one more reason to read <limits.h> instead of computing powers of two.
Common mistakes
Storing a value like 100000 in an int because int is assumed to be 32 bits: on a 16-bit-int target the value does not fit, so only that build produces wrong numbers while the desktop build looks fine.
Detecting overflow with if (a + b > INT_MAX) or if (a + b < 0): the addition is already undefined, and at -O2 the compiler may delete the test because it assumes signed overflow cannot occur.
Assuming -x and abs(x) are always safe for an int x: -INT_MIN is outside int's range, so abs(INT_MIN) is undefined and in practice usually returns INT_MIN, still negative.
Try it yourself
Change, predict, then run
Print sizeof(int) * CHAR_BIT, INT_MAX and INT_MIN, then write int mid(int a, int b) that returns the midpoint of a and b. Make sure mid(INT_MAX, INT_MAX - 2) prints 2147483646 instead of overflowing the way (a + b) / 2 does.
Open the C workspaceCheck your understanding
Why is int 32 bits rather than 64 bits on nearly every 64-bit desktop and server platform?
- 64-bit CPUs still execute 32-bit signed arithmetic faster, so a wider int would slow every program down.
- The C standard caps int at 32 bits; only long and long long are permitted to be wider.
- Each platform's ABI froze int at 32 bits so existing code, struct layouts and interfaces kept working through the 64-bit transition.
- printf's %d conversion can only carry 32 bits, so int cannot be defined any wider than that.
Show answer
The standard fixes only a lower bound (int must cover -32767..32767 and be no narrower than short), so the actual width comes from the platform's ABI, and LP64 and LLP64 both chose to leave int at 32 bits for compatibility. The tempting wrong answer is the claim that the standard caps int at 32 bits: nothing forbids a wider int, and %d simply means 'an int' at whatever width the implementation uses.