C / TYPES AND REPRESENTATION
Constants, literals and suffixes like U and L
Work out the exact type of any C constant from its base and suffix, and use U, L, LL and f deliberately to control how an expression is computed.
What you will learn
- Read U, L and LL as constraints on the constant's own type, not on the destination
- Explain why 0xFFFFFFFF can be unsigned int while 4294967295 is long
- Use an LL or U suffix to force an expression to be computed in a wider type
- Remember 1.5 is a double, 1.5f is a float, and 'A' is an int in C
Understanding Constants, literals and suffixes like U and L
Every literal you write is an expression, and like any expression it has a type. The compiler picks that type from the way the literal is spelled, before it ever looks at where the value is going. For an unsuffixed decimal constant the rule is: take the first type in the list int, long, long long that can represent the value. So in long x = 5; the constant 5 is still an int, and the assignment converts it afterwards; nothing about the variable reaches back and changes the literal.
A suffix narrows that search. U says only unsigned types are candidates, L says start no lower than long, LL says start no lower than long long, and they combine freely in either order and either case (UL, LU, ull, ULL), with the one restriction that the two L characters in LL must match. Octal and hexadecimal constants use a longer candidate list that interleaves the unsigned types at each rank, which is why 0xFFFFFFFF is an unsigned int on a machine with 32-bit int while the same value written as 4294967295 becomes a long. There is no suffix for short or char because a constant is never narrower than int; anything narrower would be widened again the instant you used it in arithmetic.
Floating constants follow the same scheme with a different default: 1.5 is a double, 1.5f is a float, 1.5L is a long double, so float scale = 0.1; rounds the decimal to the nearest double and then rounds that to a float. Character constants are another surprise for people coming from C++: 'A' has type int in C, not char. All of this matters because the literal's type decides the type of the whole expression it participates in, which makes a suffix the cheapest way to widen a computation: secs * 1000000LL multiplies in long long, while secs * 1000000 can overflow in int no matter how wide the variable receiving the result is.
placeholder
<stdio.h>
/* _Generic (C11) asks the compiler what type an expression has. */
TYPENAME(x)
int main(void)
{
printf("%-11s %s\n", "42", TYPENAME(42));
printf("%-11s %s\n", "42u", TYPENAME(42u));
printf("%-11s %s\n", "42L", TYPENAME(42L));
printf("%-11s %s\n", "42ULL", TYPENAME(42ULL));
printf("%-11s %s\n", "4294967295", TYPENAME(4294967295));
printf("%-11s %s\n", "0xFFFFFFFF", TYPENAME(0xFFFFFFFF));
printf("%-11s %s\n", "'A'", TYPENAME('A'));
printf("%-11s %s\n", "1.5", TYPENAME(1.5));
printf("%-11s %s\n", "1.5f", TYPENAME(1.5f));
printf("%-11s %s\n", "1.5L", TYPENAME(1.5L));
return 0;
}The spelling of a constant, its base and its suffix, fixes its type before any assignment happens, and that type drives the arithmetic around it.
Worked examples
The leading zero changes the base
Shows how a decimal number padded with a zero silently becomes octal.
<stdio.h>
int main(void)
{
int timeout = 0500; /* leading zero: read in base 8 */
int mask = 0x0F;
printf("0500 -> %d\n", timeout);
printf("500 -> %d\n", 500);
printf("0x0F -> %d\n", mask);
printf("0755 -> %d\n", 0755);
return 0;
}Example explained
Line 10500 starts with a zero, so its digits are base 8: 5 * 64 gives 320, and the compiler says nothing.
Line 20x0F is base 16 and yields 15; there is no prefix meaning "decimal", plain digits are the default.
Line 30755 is the familiar Unix permission spelling, 7*64 + 5*8 + 5, which is 493 in decimal.
Line 4Only the digits 8 and 9 give the trick away: 0900 is a compile error, while 0500 compiles as a wrong value.
Why limits.h writes INT_MIN as -2147483647 - 1
Shows that the minus sign is an operator applied to a constant, not part of the literal.
<stdio.h>
<limits.h>
int main(void)
{
printf("%-16s %zu bytes\n", "2147483647", sizeof(2147483647));
printf("%-16s %zu bytes\n", "2147483648", sizeof(2147483648));
printf("%-16s %zu bytes\n", "-2147483648", sizeof(-2147483648));
printf("%-16s %zu bytes\n", "-2147483647 - 1", sizeof(-2147483647 - 1));
printf("%-16s %zu bytes\n", "INT_MIN", sizeof(INT_MIN));
printf("INT_MIN == -2147483648 is %d\n", INT_MIN == -2147483648);
return 0;
}Example explained
Line 12147483648 is one past INT_MAX, so the compiler moves to the next signed candidate and makes it a long.
Line 2-2147483648 is unary minus applied to that long constant, so the whole expression is long too; C has no negative literals.
Line 3-2147483647 - 1 stays inside int at every step, which is exactly why limits.h spells INT_MIN that way instead of writing the digits directly.
Line 4The last line shows the values agree; only the types differ, and that is what bites when you hand the constant to %d or another variadic function.
Important notes
The answers above assume 32-bit int and 64-bit long, as on 64-bit Linux and macOS. On 64-bit Windows long is 32 bits, so 4294967295 comes out as long long there; _Generic also requires C11 or later.
A const int is not an integer constant expression in C, so const int N = 8; int buf[N]; declares a variable-length array rather than a fixed one. Use a #define, an enum constant, or C23's constexpr when you need a real compile-time constant.
Common mistakes
Writing long long ns = 1000000 * 1000000; and expecting the wide variable to help: both operands are int, so the product overflows int (undefined behaviour, usually a wrapped negative number) before the assignment ever happens. Writing 1000000LL * 1000000 fixes it.
Zero-padding a decimal constant for alignment, as in int timeout = 0500;, which quietly becomes octal 320 and compiles without a warning.
Assuming 0.1 is a float: with float f = 0.1f;, the test f == 0.1 widens f back to double and compares it against the double nearest 0.1, which is a different value, so the comparison is false.
Try it yourself
Change, predict, then run
Print 24 * 60 * 60 * 1000000 and 24LL * 60 * 60 * 1000000 with %lld and compare the two results. Then explain, by naming the type of each literal and each intermediate product, why only the second one reports 86400000000.
Open the C workspaceCheck your understanding
On a platform with 32-bit int and 64-bit long, 0xFFFFFFFF has type unsigned int but 4294967295 has type long. What accounts for the difference?
- Unsuffixed hexadecimal and octal constants may take an unsigned type, while unsuffixed decimal constants are only ever signed
- Hexadecimal constants are always unsigned and decimal constants are always signed
- Hexadecimal constants are limited to 32 bits, so the compiler cannot widen 0xFFFFFFFF to long
- The compiler chooses unsigned int because all 32 bits of the hexadecimal form are written out explicitly
Show answer
The candidate list for an unsuffixed decimal constant is int, long, long long, all signed, so 4294967295 skips past int and lands on long. For hex and octal the list also includes the unsigned types at each rank, and unsigned int is the first that fits. Option 2 is tempting but wrong: 0x7F is a plain int, so the base only lengthens the candidate list, it never forces unsignedness. Adding a U suffix is what guarantees it.