Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Integer Constants in C
Decimal, octal and hexadecimal literals, the U/L/LL suffixes, and the type the compiler really picks. Part of our free embedded systems course.
Every number you type into a C program is a literal, and the whole-number ones are called integer constants in C. They look trivial, yet they hide the details that decide whether a register mask works, whether a file gets the right permissions, and whether a loop silently overflows. This lecture is the first checkpoint of our free embedded systems course and the foundation for the free Linux kernel development course and free Linux device drivers course that follow, because kernel and driver code is full of hex masks, octal modes and suffixed literals.
We will not just list rules. You will compile small programs, read the real compiler messages, predict outputs before revealing them, and finish with interview-grade questions. If you are following our free embedded Linux course in parallel, everything here applies unchanged to user-space and kernel C.
What You Will Learn
By the end of this lecture you can:
- Classify any literal as a decimal, octal or hexadecimal integer constant at a glance
- Apply the exact validity rules for integer constants in C and read the compiler’s complaint when they are broken
- Use the U, L and LL suffixes and predict the type the compiler assigns on 32-bit and 64-bit systems
- Explain why
sizeof(0x80000000)andsizeof(2147483648)differ on a 64-bit Linux machine - Write embedded-style bit masks in hexadecimal and permission values in octal
- Answer the classic interview traps about leading zeros, signed/unsigned comparison and overflow
Prerequisites
You should know what a C program looks like (a main function and printf) and have gcc installed. On Ubuntu or Debian run sudo apt install build-essential. A basic idea of binary numbers helps, but we rebuild it here from scratch.
Constants in C: The Big Picture
A constant is a value written directly in the source whose meaning is fixed before the program runs. The C standard recognises four families of such tokens: integer constants, floating constants, character constants and enumeration constants. String literals sit beside them as a fifth kind of fixed value. In this chapter we cover them one by one, and the diagram shows where integer constants fit.
Numeric constants split into integer and real; integer constants come in three bases. Character, string and real constants get their own lectures.
Rules for Writing Integer Constants in C
An integer constant is a whole number with no decimal point and no exponent. The compiler’s scanner (the lexer) accepts it only if all of these hold:
- It has at least one digit. A bare
0xwith nothing after it is an error. - It contains no spaces, commas or underscores.
98 5is two tokens and8,354is the number 8, the comma operator, then 354. - It uses only digits that belong to its base (table below).
- It has no sign of its own. In
-25the minus is the unary negation operator applied to the constant25.
| Base | How it starts | Allowed digits | Example | Value |
|---|---|---|---|---|
| Decimal | 1-9 (or the single digit 0) | 0 1 2 3 4 5 6 7 8 9 | 3705 | 3705 |
| Octal | Leading 0 | 0 1 2 3 4 5 6 7 | 0732 | 474 |
| Hexadecimal | 0x or 0X | 0-9, a-f, A-F | 0x1FA | 506 |
Decimal Integer Constants
These are the everyday numbers: 0, 123, 23759. A decimal constant never starts with 0 unless it is the number zero itself, because a leading zero is the signal that you want octal. That single rule is the source of the most famous beginner bug in C, which we will trigger on purpose in Example 3.
Octal Integer Constants
An octal constant starts with 0 and uses only the digits 0 to 7, so 077 is 7×8 + 7 = 63. Octal survives in modern code for one very practical reason: Unix permissions. Each permission group (owner, group, others) is three bits, and three bits map exactly to one octal digit. That is why you write chmod 0644 or pass 0644 as the mode argument of open() on Linux.
Writing a digit 8 or 9 after a leading zero is rejected outright. Compile int a = 09; and GCC says:
error: invalid digit "9" in octal constant
Hexadecimal Integer Constants
A hexadecimal constant starts with 0x or 0X followed by digits 0-9 and letters a-f in either case, so 0xFFF, 0xac and 0XA15B are all fine. Hardware people love hex because one hex digit is exactly four bits, so a 32-bit register is eight digits and you can read individual nibbles by eye. Datasheets, linker scripts and kernel headers are written in hex for this reason.
Hex reads the bits four at a time and octal reads them three at a time, which is why both are shorthand for binary.
| Written as | Valid? | Why |
|---|---|---|
3#5 | No | # is not part of a number |
98 5 | No | A space splits it into two tokens |
0925 | No | Leading zero makes it octal, and 9 is not an octal digit |
8,354 | No | Commas are not digit separators in C |
2.5 | Not an integer constant | It is a valid floating constant instead |
0xG1 | No | G is not a hexadecimal digit |
0xfaU | Yes | Hex with an unsigned suffix |
Newer C
The C23 standard added binary constants such as 0b1010 and the digit separator ' (as in 1'000'000). GCC has long accepted 0b as an extension, but if your code must build on older toolchains, stick to hex.
Integer Constant Types and Suffixes
Writing 7 does not just give you the value seven. The compiler also attaches a type. Without a suffix, it walks down a list of candidate types and picks the first one that can hold the value. Suffixes let you steer that choice yourself.
| Suffix | Meaning | Decimal constant tries | Octal / hex constant tries |
|---|---|---|---|
| none | plain | int, long, long long | int, unsigned int, long, unsigned long, long long, unsigned long long |
U | unsigned | unsigned int, unsigned long, unsigned long long | same as decimal |
L | long | long, long long | long, unsigned long, long long, unsigned long long |
UL | unsigned long | unsigned long, unsigned long long | same as decimal |
LL | long long | long long | long long, unsigned long long |
ULL | unsigned long long | unsigned long long | same as decimal |
Notice the asymmetry. A decimal constant never silently becomes unsigned, but an octal or hex constant can. That is the trick behind the interview favourite in Example 2. Suffix letters are case-insensitive and the order UL or LU does not matter, but write the L in upper case: 10l looks far too much like 101.
| Platform | int | long | long long | Typical where |
|---|---|---|---|---|
| ILP32 | 4 bytes | 4 bytes | 8 bytes | 32-bit Linux, most Cortex-M microcontrollers |
| LP64 | 4 bytes | 8 bytes | 8 bytes | 64-bit Linux and macOS |
| LLP64 | 4 bytes | 4 bytes | 8 bytes | 64-bit Windows |
| 16-bit MCU | 2 bytes | 4 bytes | 8 bytes | AVR, MSP430, some 8051 compilers |
The consequence is portability: the literal 40000 is an int on a 32-bit chip but a long on a 16-bit one. All examples below were run on 64-bit Linux (LP64) with GCC 13.
Hands-On: Four Programs on Integer Constants in C
Example 1: Same value, three bases
Goal
Prove that 100, 0144 and 0x64 are the same number and print it in all three bases.
Save this as int_bases.c:
#include <stdio.h>
int main(void)
{
int dec = 100;
int oct = 0144;
int hex = 0x64;
printf("dec=%d oct=%d hex=%d\n", dec, oct, hex);
printf("100 printed in octal : %o\n", dec);
printf("100 printed in hex : %x\n", dec);
printf("all three equal? %s\n", (dec == oct && oct == hex) ? "yes" : "no");
return 0;
}
Build and run:
$ gcc -Wall -Wextra -std=c11 -o int_bases int_bases.c
$ ./int_bases
Interactive: Predict the output first, then click to check yourself.
Expected output:
dec=100 oct=100 hex=100
100 printed in octal : 144
100 printed in hex : 64
all three equal? yes
%dprints decimal,%oprints octal (without the leading zero) and%xprints lower-case hex (without0x).- The comparison is true because the base only changes how you write the number, never the stored bits.
- Use
%#oand%#xif you want the prefixes printed too.
Example 2: Which type did I get?
Goal
Print sizeof of several literals to see suffixes and the decimal-versus-hex difference in action.
Save this as int_types.c:
#include <stdio.h>
int main(void)
{
printf("sizeof(7) = %zu\n", sizeof(7));
printf("sizeof(7U) = %zu\n", sizeof(7U));
printf("sizeof(7L) = %zu\n", sizeof(7L));
printf("sizeof(7UL) = %zu\n", sizeof(7UL));
printf("sizeof(7LL) = %zu\n", sizeof(7LL));
printf("sizeof(2147483648) = %zu\n", sizeof(2147483648));
printf("sizeof(0x80000000) = %zu\n", sizeof(0x80000000));
return 0;
}
Build and run:
$ gcc -Wall -Wextra -std=c11 -o int_types int_types.c
$ ./int_types
Interactive: Predict the output first, then click to check yourself.
Expected output:
sizeof(7) = 4
sizeof(7U) = 4
sizeof(7L) = 8
sizeof(7UL) = 8
sizeof(7LL) = 8
sizeof(2147483648) = 8
sizeof(0x80000000) = 4
Read the last two lines slowly. 2147483648 is one more than the largest 32-bit int. As a decimal constant it may not become unsigned, so it skips to long (8 bytes). 0x80000000 is the same value, but as a hex constant it is allowed to try unsigned int first, and it fits there, so you get 4 bytes. Same number, two different types. This is exactly why mixed signed/unsigned expressions with hex literals behave differently from decimal ones.
Example 3: The leading-zero trap
Goal
See how a harmless-looking leading zero changes the value of a number.
Save this as octal_trap.c:
#include <stdio.h>
int main(void)
{
int slot_a = 007;
int slot_b = 010;
int slot_c = 012;
printf("007 -> %d\n", slot_a);
printf("010 -> %d\n", slot_b);
printf("012 -> %d\n", slot_c);
printf("sum of the three = %d\n", slot_a + slot_b + slot_c);
return 0;
}
Build and run:
$ gcc -Wall -Wextra -std=c11 -o octal_trap octal_trap.c
$ ./octal_trap
Interactive: Predict the output first, then click to check yourself.
Expected output:
007 -> 7
010 -> 8
012 -> 10
sum of the three = 25
Anyone padding numbers for neat alignment (007, 010, 012) accidentally writes octal. 010 is 8 and 012 is 10, so the sum is 7 + 8 + 10 = 25 and not what a column of “neat” decimals would suggest. Worse, 09 or 08 will not even compile, so the bug only hides when every digit happens to be below 8. Never pad decimal numbers with leading zeros in C source.
Example 4: Hex flags for LEDs
Goal
Use hex constants as bit masks, the way real firmware controls pins and status registers.
Save this as led_flags.c:
#include <stdio.h>
int main(void)
{
unsigned int leds = 0x00;
leds |= 0x01; /* red on */
leds |= 0x04; /* blue on */
printf("after set : 0x%02X\n", leds);
leds &= ~0x01u; /* red off */
printf("after clear red : 0x%02X\n", leds);
printf("green on? %s\n", (leds & 0x02) ? "yes" : "no");
printf("blue on? %s\n", (leds & 0x04) ? "yes" : "no");
return 0;
}
Build and run:
$ gcc -Wall -Wextra -std=c11 -o led_flags led_flags.c
$ ./led_flags
Interactive: Predict the output first, then click to check yourself.
Expected output:
after set : 0x05
after clear red : 0x04
green on? no
blue on? yes
|=sets a bit,&= ~maskclears it, and&tests it.- Each constant has exactly one bit set (0x01, 0x02, 0x04), so flags never interfere with each other.
- The
uin~0x01ukeeps the whole expression unsigned, avoiding sign surprises when masks get wider. - On a real board the variable would be a memory-mapped register, and the same operations would switch real LEDs.
Common Mistakes and Troubleshooting
| Mistake | What happens | Fix |
|---|---|---|
Zero-padding decimals (007, 010) | Silently becomes octal, or fails on 8 and 9 | Never lead with 0 unless you want octal |
1 << 31 on a 32-bit int | Signed overflow, undefined behaviour | Write 1u << 31 or 1UL << 31 |
1000 * 60 * 60 * 24 * 365 | Overflows int before assignment, even into a long variable | Make one operand long: 1000LL * 60 * 60 * 24 * 365 gives 31536000000 |
Comparing -1 < 1U | Evaluates to 0 (false) because -1 converts to a huge unsigned value | Keep both sides the same signedness |
Assuming long is 4 bytes | Breaks on LP64 systems | Use <stdint.h> types and UINT32_C() style macros |
Typing int x = 4294967296; | GCC warns that the value changes from 4294967296 to 0 | Use a wider type or fix the number |
Best Practices, Performance and Security
- Hex for bit patterns, decimal for counts, octal only for Unix modes. Matching the base to the meaning makes code self-explanatory.
- Name your constants.
0x40021000in ten places is a bug waiting to happen; Lecture 4 shows how symbolic constants fix that. - Suffix on purpose. Use
Uon masks andULorULLwhen the value must be wide. - Performance: expressions made only of constants, like
60 * 60 * 24, are folded by the compiler at build time and cost nothing at run time, so favour readable arithmetic over pre-computed magic numbers. - Security: signed integer overflow is undefined behaviour, and size calculations such as
count * 4that wrap around are a classic source of buffer overflows. Do size maths insize_twith an unsigned or wide constant.
Quiz: Integer Constants in C
Try each question on paper first. Click a question to reveal the answer.
Q1. Which of these are valid integer constants: 250, 0372, 0xFA, 0xfaU, 1,000, 08?
Answer: Four are valid: 250, 0372, 0xFA and 0xfaU. All four have the value 250 (0372 is 3×64 + 7×8 + 2). 1,000 is not one constant (it is 1, a comma, then 000), and 08 has an invalid octal digit.
Q2. What is the value of 011 + 0x11 + 11?
Answer: 37. That is 9 (octal 011) + 17 (hex 0x11) + 11 (decimal).
Q3. What does printf("%d", 010 + 10); print?
Answer: 18, because 010 is octal and equals 8.
Q4. What is the type of 5000000000 on 64-bit Linux, and on a 32-bit system?
Answer: long (8 bytes) on LP64. On a 32-bit system where long is 4 bytes it becomes long long. Both can hold the value.
Q5. Does -1 < 1U evaluate to true or false?
Answer: False (0). The usual arithmetic conversions turn -1 into an unsigned int, which is 4294967295, and that is not less than 1.
Q6. Write the largest 32-bit unsigned value as a hex constant.
Answer: 0xFFFFFFFF, which is 4294967295. On a platform with 32-bit int it has type unsigned int.
Q7. Which line fails to compile: int a = 0xAG;, int b = 0x;, int c = 00;, int d = 07;?
Answer: The first two. G is not a hex digit, and 0x with no digits is an invalid suffix error. 00 and 07 are valid octal.
Q8. Why do sizeof(0x80000000) and sizeof(2147483648) differ on LP64?
Answer: Hex and octal constants may be typed as unsigned int, so 0x80000000 fits into 4 bytes. A decimal constant never becomes unsigned and must jump to long, giving 8 bytes.
Interview Questions: Integer Constants in C
These come up in embedded and product-company interviews. Answer out loud, then click.
Q1. Is 0 an octal or a decimal constant?
Answer: Per the C grammar, a lone 0 is formally an octal constant. The value is the same either way, which makes this a trivia question that tests whether you know leading zero means octal.
Q2. Why add UL to a literal in embedded code?
Answer: To control width and signedness. On 16-bit targets 40000 will not fit int, and 1 << 31 is undefined for a 32-bit signed int. 1UL << 31 is well defined on every platform.
Q3. Is there any difference between writing 255 and 0xFF?
Answer: None at compile time. Both are decimal-or-hex spellings of the same value and both have type int. Only readability differs, and hex wins for masks.
Q4. What happens with int x = 4294967296; on a 64-bit machine?
Answer: The constant is a long. Converting it to int does not fit, so the value is implementation-defined; GCC warns that it changes to 0.
Q5. How do you write a binary constant portably?
Answer: In C23 use 0b101. Before that, 0b is only a compiler extension, so the portable choice is hex, where each digit is four bits.
Q6. A colleague writes int mode = 0644; for open(). Is it a bug?
Answer: No, it is correct and intentional: the leading zero makes it octal, and 0644 means owner read/write, group and others read-only. Replacing it with decimal 644 would produce the wrong permission bits.
Summary and Key Takeaways
- An integer constant is a whole number with at least one digit, no spaces or commas, and no sign of its own.
- Decimal never starts with 0, octal starts with 0 and uses 0-7, hexadecimal starts with 0x and uses 0-9 and a-f.
- Without a suffix the compiler picks the first fitting type from a list, and octal or hex lists include unsigned types.
- Use U, L, UL, LL and ULL deliberately; write the L in upper case.
- Hex is for bit masks, octal is for Unix permissions, decimal is for everything else.
- Sizes of int and long depend on the platform, so never assume them in portable code.
Conclusion
Integer constants in C are small, but they sit at the exact point where text becomes machine values, so every misunderstanding here turns into a hard-to-find bug later: a wrong permission, a masked-off flag, an overflowing timer. You now know how to read any literal, which base it uses, and which type the compiler gives it, and you have verified the surprising cases yourself by running code.
Next we move from whole numbers to real numbers, where exponent notation and precision introduce a different set of traps. Keep practising by changing the examples: add a 0b constant, try %lu with 7UL, or write your own mask table for a GPIO port.
Frequently Asked Questions
1. Is this free embedded systems course really free?
Answer: Yes. Every lecture, example and quiz on EmbeddedPathashala is free, with no signup wall. The C lectures prepare you for the Linux kernel and device driver lectures.
2. Do I need prior programming experience for this free embedded Linux course material?
Answer: Basic familiarity with any programming language is enough. We explain each term as it appears.
3. What is the difference between a constant and a variable in C?
Answer: A constant is a fixed value written in the source, like 42. A variable is a named storage location whose value can change. Lecture 5 covers variables.
4. Why does 010 print 8?
Answer: A leading zero marks an octal constant, and octal 10 equals 8 in decimal.
5. Which suffix should I use for 64-bit values?
Answer: Use ULL or LL for guaranteed 64-bit constants, or the UINT64_C() macro from stdint.h.
6. Can an integer constant be negative?
Answer: Technically no. The minus sign is the unary negation operator applied to a positive constant, which matters in corner cases like the most negative int.
7. Will this help with the Linux kernel development course?
Answer: Yes. Kernel headers define registers with hex constants, use octal modes, and rely on suffixes such as UL for 64-bit-safe masks.
Keep Building Your C Foundation
Finish the other lectures in this free embedded systems course, then continue to the free Linux device drivers course. Re-run each example and change one thing at a time; that is how these rules stick.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments