Question
What Does `:-!!` Mean in C? Bit-Fields, Compile-Time Errors, and Linux Macros
Question
I came across this macro in Linux header code and want to understand the expression :-!!(e):
/* Force a compilation error if condition is true, but also produce a
result (of value 0 and type size_t), so the expression can be used
e.g. in a structure initializer (or where-ever else comma expressions
aren't permitted). */
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
What does :-!! mean in this context?
Note: in newer Linux versions, this macro has been moved to /usr/include/linux/build_bug.h.
Short Answer
By the end of this page, you will understand how :-!!(e) works in C, why it appears inside Linux kernel macros, and how it uses a bit-field with an invalid width to deliberately trigger a compile-time error when a condition is true.
Concept
In this macro, :-!!(e) is not a single C operator. It is a combination of two separate pieces of C syntax:
:— the separator used in a bit-field declaration inside astruct!!(e)— a common C idiom that converts any non-zero value to1and zero to0
So this code:
struct { int:-!!(e); }
is declaring an unnamed bit-field of type int with width -!!(e).
Why does that matter?
In C, a bit-field width must be a non-negative integer constant expression in valid use cases. If the width becomes negative, the compiler rejects it.
Now look at the logic:
- If
eis0, then!!(e)becomes0, so-!!(e)becomes0
Mental Model
Think of this like a security gate in the compiler.
!!(e)turns the condition into a clean yes/no value:- no →
0 - yes →
1
- no →
- the leading minus sign flips that into:
- no →
0 - yes →
-1
- no →
Then the bit-field width acts like a rule:
- width
0→ allowed - width
-1→ forbidden
So the compiler is being asked:
“Create a tiny struct with a field width of either
0or-1.”
If the answer is -1, the compiler refuses. That refusal is the whole point.
Another way to think about it:
!!(e)is a boolean normalizer-turns true into an illegal width- the bit-field declaration turns that illegal value into a compile-time failure
Syntax and Examples
The key syntax
struct {
int : width;
}
This declares an unnamed bit-field with the given width.
In the Linux macro, the width is computed:
int : -!!(e);
Understanding !!
int a = !!0; // 0
int b = !!5; // 1
int c = !!-10; // 1
!!x is a common C trick:
- first
!xconverts zero to1, non-zero to0 - second
!flips that again - result is normalized to
0or1
Small example
Step by Step Execution
Consider this example:
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int : -!!(e); }))
size_t value = BUILD_BUG_ON_ZERO(4);
Step 1: Substitute the macro
The preprocessor expands it to:
size_t value = sizeof(struct { int : -!!(4); });
Step 2: Evaluate !!(4)
!(4) // 0
!!(4) // 1
So now we have:
size_t value = sizeof(struct { int : -1; });
Step 3: Parse as a bit-field
Inside the struct, this means:
- unnamed bit-field
- type
int - width
-1
Step 4: Compiler validates the bit-field width
Real World Use Cases
Compile-time error macros like this are used when a broken assumption must stop the build immediately.
Common situations
-
Structure layout checks
- Ensure a struct has the expected size
- Useful in kernels, drivers, embedded code, and binary protocols
-
Alignment validation
- Check that offsets or sizes are multiples of a required alignment
-
Array and buffer constraints
- Reject code if a fixed buffer size is too small
-
Platform-specific assumptions
- Verify integer widths, pointer sizes, or hardware constants
-
Constant configuration checks
- Ensure one compile-time constant is not larger than another
Example
#define MUST_BE_8_BYTES(type) \
((void)sizeof(struct { int : -!!(sizeof(type) != 8); }))
This could be used to ensure a type has exactly 8 bytes.
Why this matters
In low-level code, runtime checks may be too late. If memory layout is wrong, the program may already be unsafe. A compile-time failure is much better.
Real Codebase Usage
In real projects, developers use compile-time checks to protect assumptions that should never be violated.
Common patterns
Guarding invariants
#define STATIC_CHECK(cond) sizeof(struct { int : -!!(cond); })
Used when a condition must remain false.
Validating constants in headers
Header files often need checks that work in expression contexts, not just statements.
int table[10 + STATIC_CHECK(MY_LIMIT > 10)];
Defensive macro design
Linux-style macros often try to:
- fail early
- work in initializers or declarations
- avoid generating runtime code
Modern replacements
In newer code, developers may prefer:
_Static_assertin C11+static_assertin C++
Example:
_Static_assert(sizeof(long) == 8, "long must be 8 bytes");
This is clearer than the bit-field trick, but older code and portability concerns explain why the macro pattern exists.
Common Mistakes
1. Thinking :-!! is a special operator
It is not.
This is wrong thinking:
// There is no C operator named :-!!
What is really happening:
:belongs to bit-field syntax-is unary minus!!converts a value to0or1
2. Forgetting that this is inside a struct field declaration
This only makes sense in a bit-field context.
struct {
int : -!!(e);
};
Outside a bit-field declaration, the colon would not mean the same thing.
3. Assuming it runs at runtime
This is not runtime logic.
Broken mental model:
if (e) {
// maybe it crashes later
}
Actual behavior:
- the compiler checks the declaration while compiling
- if invalid, build fails before the program runs
Comparisons
Compile-time tricks compared
| Technique | Purpose | Works in expression context? | Readability | Typical use |
|---|---|---|---|---|
| Bit-field width trick | Force compile-time error | Yes | Low | Older C macros, kernel-style code |
sizeof(char[(cond) ? 1 : -1]) | Force compile-time error | Yes | Medium | Older C compile-time assertions |
_Static_assert(cond, msg) | Compile-time assertion | No, it is a declaration | High | Modern C11+ code |
if (...) | Runtime branching | No | High |
Cheat Sheet
Quick meaning
int : -!!(e)
means:
!!(e)→ converteto0or1-!!(e)→ becomes0or-1- used as a bit-field width
- width
-1causes a compile-time error
Expansion logic
e | !!(e) | -!!(e) | Result |
|---|---|---|---|
0 | 0 |
FAQ
What does :-!! mean in C?
It is not a single operator. It is a bit-field colon : followed by the expression -!!(e), which evaluates to 0 or -1.
Why does !!(e) appear in C code?
It converts any non-zero value to 1 and zero to 0. This is a common C idiom when code needs a clean boolean-like integer.
Why does a negative bit-field width cause an error?
Because bit-field widths must be valid non-negative widths in this usage. A negative width is invalid, so the compiler rejects the code.
What is BUILD_BUG_ON_ZERO used for?
It is used to force a compile-time failure when a condition is true, while still behaving like an expression.
Is this still used in modern C?
Older codebases and low-level projects still use patterns like this, but modern C usually prefers _Static_assert when possible.
Is :-!! Linux-specific syntax?
No. The syntax is standard C components used in a clever way. The macro itself is common in Linux-style low-level C code.
Can I replace this with ?
Mini Project
Description
Build a small compile-time validation header that checks assumptions about constants and type sizes. This demonstrates how expression-based compile-time checks work and helps you compare the older bit-field trick with clearer modern alternatives.
Goal
Create a C program that compiles only when a few predefined conditions are valid, and fails to compile when they are not.
Requirements
- Define a macro that uses the bit-field-width trick to trigger a compile-time error.
- Use the macro in at least two checks involving constants or
sizeof. - Print a success message only if the program compiles.
- Include one commented example that would intentionally fail if uncommented.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
Definition vs Declaration in C and C++: What’s the Difference?
Learn the difference between declarations and definitions in C and C++ with simple examples, common mistakes, and practical usage.
Difference Between #include <...> and #include "..." in C and C++
Learn the difference between #include with angle brackets and quotes in C and C++, including search paths, examples, and common mistakes.