C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
245: Binary Integer Literals and Digit Separators
I've spent a lot of time reviewing code from developers who are transitioning from Python or Java to C, and there's a recurring pattern I see. When they need to define a bitmask—say, for a hardware register or a set of flags—they often write a comment next to a hexadecimal value to explain what the bits actually are. They'll write something like uint8_t mask = 0x45; // 01000101 because they believe C doesn't actually support binary literals.
The "Binary Isn't Native" Myth
For decades, that misconception was actually true. If you were using C89 or C99, you couldn't just type binary into your source code; you had to use hex or octal and do the mental math. This led to a lot of "magic numbers" in codebases that were incredibly hard to audit without a calculator handy. If you tried to write 0b01000101 in an old compiler, it would simply throw a syntax error.
But we're not in the 90s anymore. With the C23 standard, binary literals are officially part of the language. You no longer have to maintain those tedious comments or guess which hex digit corresponds to which bit.
Speaking Binary with 0b
Now, you can prefix any integer literal with 0b or 0B to tell the compiler, "I'm thinking in bits." This is a game-changer when you're doing "bit-banging" or configuring specific pins on a microcontroller.
// The old, annoying way
uint8_t status_mask = 0x8A;
// The modern, clear way
uint8_t status_mask = 0b10001010;
I always recommend using binary literals whenever the position of the bit is more important than the value of the number. If you're checking if the 7th bit is high, 0b10000000 is infinitely more readable than 0x80.
Stopping the Eye-Strain with Digit Separators
Once you start working with 32-bit or 64-bit registers, binary literals introduce a new problem: the "wall of digits." Trying to count zeros in 0b10101100110100101111000010101101 is a recipe for a production bug. You'll blink, lose your place, and accidentally flip a bit.
C23 solved this by introducing the digit separator: the single quote ('). This character is completely ignored by the compiler, but it allows you to group bits into readable chunks—usually nibbles (4 bits) or bytes (8 bits).
// Hard to read, easy to mess up
uint32_t ip_mask = 0b11111111111111110000000000000000;
// Clean, organized, and audit-friendly
uint32_t ip_mask = 0b11111111'11111111'00000000'00000000;
I personally prefer grouping by four. It keeps the relationship to hexadecimal clear while giving me the granular control of binary. Just remember: the separator can be used in decimal, hex, and octal literals too, not just binary. It's a general tool for making large numbers human-readable.
📋 Practical Task
Exercise: Configuring a Virtual Hardware Control Register
You are writing a driver for a virtual device. The device has a 16-bit control register where specific bits enable different features. Your task is to define a uint16_t variable named device_config using a binary literal and digit separators to set the following state:
- The most significant 4 bits (the "mode" bits) must be
1011. - The next 4 bits (the "interrupt" bits) must be
0001. - The next 4 bits (the "clock" bits) must be
1100. - The least significant 4 bits (the "power" bits) must be
0101.
Write the code to initialize this variable in a single line, ensuring you use the ' separator to group the bits into four-bit nibbles for readability.
There are no comments for now.