C / TYPES AND REPRESENTATION
long, short and choosing an integer width
Pick between short, int, long and long long by the range your values need, print each with the right length modifier, and know why long differs by platform.
What you will learn
- State the minimum guaranteed ranges of short, long and long long from memory
- Print each width correctly with %hd, %ld and %lld instead of guessing
- Reach for long long, not long, when a value can pass 2,147,483,647
- Use short only to shrink large arrays or structs, never to gain speed
Understanding long, short and choosing an integer width
C does not tell you how big short, int, long and long long are. It only fixes a minimum range for each and an ordering between them: short holds at least -32768..32767, long at least -2147483647..2147483647, long long at least -(2^63-1)..2^63-1, and no type in that list is narrower than the one before it. So long is a floor, not a promise of 64 bits: on Linux and macOS it is 64 bits (the LP64 model), while on 64-bit Windows it stays 32 bits (LLP64) and long long is the only 64-bit choice. The mental model is a ranked set of slots whose concrete sizes are chosen by the platform's ABI, and limits.h is where that platform states its answer.
Because a type name is only a range request, the width also has to be spelled out in two other places. Integer constants have their own type, so 1000 * 60 * 60 * 24 * 365 is computed entirely in int and overflows even when you assign it to a long long; the L and LL suffixes exist to push one operand up so the whole chain is evaluated wide. And printf takes its arguments through a variable argument list that carries no type information, so %hd, %ld and %lld are how you tell it how many bytes to read; feeding a long long to %d is undefined behaviour, not a formatting nit.
For actually choosing, start from the values, not the names. Default to int for loop counters, indexes and small quantities. Jump straight to long long for anything that can outgrow roughly two billion: byte counts, file offsets, milliseconds since an epoch, money in cents. Reserve short for cases where you have very many of them, such as a large sample buffer or a packed struct, since arithmetic on short is carried out at int width anyway and buys no speed. That leaves long as the awkward middle: pick it mainly when an API you call already uses long.
<stdio.h>
<limits.h>
int main(void)
{
short port = 8080; /* comfortably inside 16 bits */
long count = 2000000000L; /* inside long's guaranteed range */
long long total = 9000000000LL; /* needs more than 32 bits */
printf("short range: %d .. %d\n", SHRT_MIN, SHRT_MAX);
printf("long range: %ld .. %ld\n", LONG_MIN, LONG_MAX);
printf("long long range: %lld .. %lld\n", LLONG_MIN, LLONG_MAX);
printf("port=%hd count=%ld total=%lld\n", port, count, total);
return 0;
}
The integer type names guarantee a minimum range and an ordering, never an exact size, so choose by the range your values need and let limits.h tell you what the platform actually gave you.
Worked examples
What short is really for
Storing many small values in short halves the memory compared with int, and the values survive because they stay inside short's guaranteed range.
<stdio.h>
int main(void)
{
short samples[4096];
int widened[4096];
int i;
for (i = 0; i < 4096; i++) {
samples[i] = i - 2048;
widened[i] = i - 2048;
}
printf("short array: %zu bytes\n", sizeof samples);
printf("int array: %zu bytes\n", sizeof widened);
printf("samples[0]=%hd widened[0]=%d\n", samples[0], widened[0]);
return 0;
}
Example explained
Line 1samples[i] = i - 2048 stores values from -2048 to 2047, well inside short's guaranteed -32768..32767, so no value is altered.
Line 28192 versus 16384 bytes shows short is 2 bytes and int is 4 on this build, so the short buffer costs half as much memory.
Line 3%hd tells printf to interpret the argument as a short; here %d would print the same number because the value was widened to int on its way into printf.
Line 4The subtraction itself happens at int width regardless of how samples is declared, which is why short buys footprint and not arithmetic speed.
Asking which type can hold the value
A millisecond count for one year needs more than 31 value bits, and limits.h answers whether int and long can take it on this platform.
<stdio.h>
<limits.h>
int main(void)
{
long long ms_per_year = 1000LL * 60 * 60 * 24 * 365;
printf("ms per year : %lld\n", ms_per_year);
printf("fits in int : %s\n", ms_per_year <= INT_MAX ? "yes" : "no");
printf("fits in long : %s\n", ms_per_year <= LONG_MAX ? "yes" : "no");
printf("fits in llong : %s\n", ms_per_year <= LLONG_MAX ? "yes" : "no");
return 0;
}
Example explained
Line 11000LL makes the first factor long long, so every following multiplication is performed at 64 bits; without the suffix the product would be an int computation that overflows.
Line 2ms_per_year <= INT_MAX compares long long with int, the int side converts up to long long, so the comparison is exact rather than truncated.
Line 331536000000 is over fourteen times INT_MAX, which is why the int line says no.
Line 4The long line says yes only because long is 64 bits here; a 64-bit Windows build prints no, which is the reason to write long long when you mean 64 bits.
Important notes
The ranges in the main output come from a build where long is 64 bits (Linux, macOS). A 64-bit Windows build prints -2147483648 .. 2147483647 on the long line, and both builds conform to the standard.
short and long are shorthand for short int and long int, and the int is normally left out; long long was added in C99, so very old compilers may reject it.
Common mistakes
Assuming long is 64 bits because it is on Linux: the same source built for 64-bit Windows caps that variable at 2147483647, so file offsets past 2 GB and epoch millisecond timestamps silently go wrong.
Writing long long total = 1000 * 60 * 60 * 24 * 365; the destination type never widens the arithmetic, so the product overflows in int before the assignment happens and the stored value is garbage.
Printing a long or long long with %d, or a short with %ld: printf reads the wrong number of bytes from the argument list, which is undefined behaviour and can corrupt every later conversion in the same call.
Try it yourself
Change, predict, then run
Declare a short, a long and a long long, assign each the largest value the standard guarantees for it (32767, 2147483647, 9223372036854775807LL), and print all three with the correct length modifiers. Then print LONG_MAX and say whether your compiler's long is wider than the guarantee.
Open the C workspaceCheck your understanding
On a machine where int is 32 bits, the statement long long ms = 1000 * 60 * 60 * 24 * 365; stores a wrong value. What explains it?
- All five factors have type int, so the whole product is computed in int and overflows before it is ever assigned
- long long is only 32 bits on some platforms, so the result cannot be stored there
- The assignment narrows the result to 32 bits because the compiler converts the right side after truncating it
- Decimal constants above 32767 are invalid without an L suffix, so the compiler substitutes a smaller value
Show answer
The type of an expression is determined by its operands, not by what it is assigned to. Each of 1000, 60, 24 and 365 fits in int, so every multiplication is int arithmetic and 31536000000 cannot be represented; writing 1000LL * 60 * 60 * 24 * 365 fixes it by pushing the chain to 64 bits. Option two is tempting but backwards: long long is at least 64 bits on every conforming implementation, so the destination was never the limitation.