Question

What Does `:-!!` Mean in C? Bit-Fields, Compile-Time Errors, and Linux Macros

clinuxmacrosoperators

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 a struct
  • !!(e) — a common C idiom that converts any non-zero value to 1 and zero to 0

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 e is 0, then !!(e) becomes 0, so -!!(e) becomes 0
  • If e is non-zero, then !!(e) becomes 1, so -!!(e) becomes -1

That means:

  • e == 0 → bit-field width is 0 → valid enough for this macro pattern
  • e != 0 → bit-field width is -1 → invalid → compile-time error

This is a clever trick used to make the compiler fail during compilation, not at runtime.

Why Linux uses this

The Linux kernel often needs to verify assumptions at compile time, such as:

  • structure sizes
  • alignment requirements
  • constant relationships
  • architecture-specific constraints

If such an assumption is wrong, the code should not compile at all. Macros like BUILD_BUG_ON_ZERO(e) exist for exactly that purpose.

Why wrap it in sizeof

The macro is written like this:

sizeof(struct { int:-!!(e); })

Using sizeof gives an expression result without creating an actual object. It also lets the macro be used in places where a normal statement would not be allowed, such as inside initializers.

The important idea is:

  • valid condition → expression compiles and produces a value
  • invalid condition → compiler errors out immediately

This is an old and very practical metaprogramming technique in C before more modern compile-time checking tools became common.

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
  • the leading minus sign flips that into:
    • no → 0
    • yes → -1

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 0 or -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
Take Quiz

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 !x converts zero to 1, non-zero to 0
  • second ! flips that again
  • result is normalized to 0 or 1

Small example

#include <stdio.h>

#define CHECK_ZERO(e) sizeof(struct { int : -!!(e); })

int main(void) {
    size_t ok = CHECK_ZERO(0);
    printf("ok = %zu\n", ok);
    return 0;
}

This compiles because:

  • e is 0
  • !!(0) is 0
  • -0 is 0

So the bit-field width is 0.

Example that fails

#define CHECK_ZERO(e) sizeof(struct { int : -!!(e); })

int x = CHECK_ZERO(123);

This fails because:

  • !!(123) is 1
  • -1 is invalid as a bit-field width

Why not just use if?

Because if is runtime logic. This macro is for conditions that should be rejected before the program can even be built.

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

A negative bit-field width is invalid, so compilation fails.


Now compare with:

size_t value = BUILD_BUG_ON_ZERO(0);

Step 1: Expand the macro

size_t value = sizeof(struct { int : -!!(0); });

Step 2: Evaluate !!(0)

!(0)   // 1
!!(0)  // 0

So this becomes:

size_t value = sizeof(struct { int : 0; });

Step 3: Validate width

Width 0 is acceptable for this pattern, so the expression compiles.

Step 4: sizeof(...) produces a value

The exact size is not the main point here. The real purpose is that the expression exists and compilation succeeds only when the condition is false.

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.

Take Quiz

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_assert in C11+
  • static_assert in 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.

Related real-world practices

  • validating ABI compatibility
  • checking protocol header sizes
  • preventing invalid configuration combinations
  • asserting assumptions in embedded firmware

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 to 0 or 1

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

4. Using non-constant expressions where compile-time checks are expected

Many compile-time tricks only work correctly when the condition can be evaluated at compile time.

Example:

int x = 5;
size_t y = sizeof(struct { int : -!!(x); });

This may not behave as intended because bit-field widths are expected to be integer constant expressions.

5. Confusing !x with !!x

!5   // 0
!!5  // 1
  • !x means logical NOT
  • !!x means normalize to boolean-like 0 or 1

6. Reusing this trick in modern C when _Static_assert is available

This older pattern is clever, but often less readable.

Prefer this when possible:

_Static_assert(sizeof(int) == 4, "int must be 4 bytes");

Use the old macro style only when you specifically need expression-context behavior or compatibility with older code.

Comparisons

Compile-time tricks compared

TechniquePurposeWorks in expression context?ReadabilityTypical use
Bit-field width trickForce compile-time errorYesLowOlder C macros, kernel-style code
sizeof(char[(cond) ? 1 : -1])Force compile-time errorYesMediumOlder C compile-time assertions
_Static_assert(cond, msg)Compile-time assertionNo, it is a declarationHighModern C11+ code
if (...)Runtime branchingNoHighRuntime validation

!x vs !!x

ExpressionIf x == 0If x != 0
!x10
!!x01
-!!x0-1

Bit-field trick vs _Static_assert

#define OLD_CHECK(e) sizeof(struct { int : -!!(e); })
_Static_assert(sizeof(long) == 8, "long must be 8 bytes");

When to use which

  • Use _Static_assert for clarity in modern C
  • Use the older trick only when you need:
    • compatibility with older compilers
    • expression-context usage
    • kernel-style macro patterns
Take Quiz

Cheat Sheet

Quick meaning

int : -!!(e)

means:

  • !!(e) → convert e to 0 or 1
  • -!!(e) → becomes 0 or -1
  • used as a bit-field width
  • width -1 causes a compile-time error

Expansion logic

e!!(e)-!!(e)Result
000compiles
non-zero1-1compile-time error

Core pattern

#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int : -!!(e); }))

Why sizeof is used

  • produces an expression value
  • avoids runtime code
  • allows use in more places than a statement macro

Related modern syntax

_Static_assert(condition, "message");

Important facts

  • :-!! is not one operator
  • : is from bit-field syntax
  • !!x is a boolean-normalizing idiom
  • negative bit-field width is invalid
  • this is a compile-time metaprogramming trick

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 _Static_assert?

Often yes, if your compiler supports C11 or later and you do not need expression-context behavior.

Why not just use assert()?

assert() is a runtime check. BUILD_BUG_ON_ZERO is a compile-time check.

Take Quiz

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

  1. Define a macro that uses the bit-field-width trick to trigger a compile-time error.
  2. Use the macro in at least two checks involving constants or sizeof.
  3. Print a success message only if the program compiles.
  4. Include one commented example that would intentionally fail if uncommented.
Take Quiz

Keep learning

Related questions

View more