What does the :-!! construct do in C programming, specifically in the context of Linux kernel macros like BUILD_BUG_ON_ZERO and BUILD_BUG_ON_NULL?
I encountered this macro code in the Linux kernel headers:
/* 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 is the purpose and functionality of the :-!! operator in this context?
Note: This macro has been moved to include/linux/build_bug.h in recent Linux kernel versions.
The :-!! construct in C programming, particularly in Linux kernel macros like BUILD_BUG_ON_ZERO and BUILD_BUG_ON_NULL, is a clever compile-time assertion technique that forces a compilation error if a given condition is true (non-zero), while also producing a valid result (zero of type size_t). This construct combines three operators in sequence: the logical double negation (!!), arithmetic negation (-), and bitfield size specification (:).
Contents
- Understanding the :-!! Components
- How BUILD_BUG_ON_ZERO Works
- BUILD_BUG_ON_NULL Macro Analysis
- Practical Examples and Use Cases
- Common Issues and Solutions
- Alternatives and Evolution
- Conclusion
Understanding the :-!! Components
The :-!! construct combines three distinct C operators that work together to create a compile-time assertion mechanism:
!! (Logical Double Negation)
The !! operator represents logical double negation:
!(logical NOT) converts any non-zero value to 0 (false) and zero to 1 (true)!!applies this twice, converting any non-zero value to 1 and zero to 0- This ensures the result is always either 0 or 1, regardless of the original expression type
int x = 5; // Non-zero value
int y = 0; // Zero value
!!x; // Results in 1
!!y; // Results in 0
- (Arithmetic Negation)
The - operator performs arithmetic negation:
- When applied to the result of
!!, it creates -1 for true conditions and 0 for false conditions - This negative value is crucial for triggering the bitfield compilation error
: (Bitfield Width Specification)
The : operator in C defines bitfields within structures:
int:-!!(e)creates an anonymous bitfield with width equal to-!!(e)- Bitfields must have non-negative widths in most C implementations
- When
-!!(e)evaluates to -1 (wheneis non-zero), this creates an invalid bitfield width
How BUILD_BUG_ON_ZERO Works
The BUILD_BUG_ON_ZERO macro is defined as:
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
Here’s how it works step by step:
- Expression Evaluation: The expression
eis evaluated at compile time - Double Negation:
!!(e)converts the result to 0 or 1 - Arithmetic Negation:
-!!(e)creates -1 (ifeis true) or 0 (ifeis false) - Bitfield Creation:
int:-!!(e)attempts to create a bitfield with negative or zero width - Compile-time Error: If
eis non-zero, the bitfield has width -1, which is invalid and causes a compilation error - Size Calculation: If
eis zero, the bitfield has width 0, which creates a zero-sized structure, andsizeofreturns 0
Key Insight: The macro returns the size of a structure containing an invalid bitfield when the condition is true, forcing a compilation error, and returns 0 when the condition is false, allowing the expression to be used in contexts where a zero value is acceptable.
BUILD_BUG_ON_NULL Macro Analysis
The BUILD_BUG_ON_NULL macro is similar but designed for null pointer checks:
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
This macro works identically to BUILD_BUG_ON_ZERO but casts the result to (void *). This makes it particularly useful in contexts where a pointer is expected, such as:
// In structure initializers or function parameters
struct example {
void *ptr;
};
struct example ex = {
.ptr = BUILD_BUG_ON_NULL(condition),
};
The macro will cause a compilation error if condition evaluates to non-zero, ensuring that null pointers are properly validated at compile time.
Practical Example in Linux Kernel:
// From Linux kernel bitfield.h
#define __BF_CHECK_POW2(n) BUILD_BUG_ON_ZERO(((n) & ((n) - 1)) != 0)
// This ensures that 'n' is a power of two by checking
// that (n & (n-1)) equals 0
Practical Examples and Use Cases
Compile-time Constant Validation
// Validate that a constant is a power of two
#define VALIDATE_POWER_OF_TWO(n) \
(sizeof(struct { int:-!!(((n) & ((n) - 1)) != 0); }))
// Usage
const int flag_size = VALIDATE_POWER_OF_TWO(32); // Works fine
const int invalid_flag = VALIDATE_POWER_OF_TWO(33); // Compilation error
Structure Initialization
struct device_config {
int flags;
void *reserved;
unsigned int timeout;
};
// Use BUILD_BUG_ON_ZERO in structure initialization
struct device_config config = {
.flags = 0x01,
.reserved = BUILD_BUG_ON_ZERO(CONFIG_FEATURE_ENABLED != 0),
.timeout = 1000,
};
Function Parameter Validation
// Function that must be called with compile-time constant
void setup_dma_channel(unsigned int channel) {
BUILD_BUG_ON_ZERO(channel >= 16); // Ensure channel is 0-15
// Rest of function implementation
}
Common Issues and Solutions
Compiler Warnings
Some compilers may produce warnings about negative bitfield widths:
warning: negative width in bit-field '<anonymous>'
This is actually the intended behavior - the warning indicates the compilation error that forces the build to fail when the condition is true.
Portability Considerations
The :-!! technique relies on C’s bitfield semantics, which can vary between compilers. Some important considerations:
- GCC Compatibility: Works well with GCC and Clang
- ICC Support: Intel Compiler also supports this construct
- MSVC Limitations: Microsoft Visual Studio may handle bitfields differently
- Compiler Version: Some older compilers may have different behavior
Alternative Error Messages
The standard error message for negative bitfield widths isn’t always descriptive. Some Linux kernel patches have aimed to improve this:
// More descriptive version (hypothetical)
#define BUILD_BUG_ON_ZERO(e) \
(sizeof(struct { int:-!!(e); }) && (void)(e))
Alternatives and Evolution
Modern C11 Static Assertions
With C11, the language introduced standardized static assertions:
#define BUILD_BUG_ON_ZERO(e) ((int)(sizeof(struct { int:-!!(e); })))
#define BUILD_BUG_ON(e) static_assert(!(e), "BUILD_BUG_ON failed")
The static_assert provides more readable error messages but can’t be used in all contexts where BUILD_BUG_ON_ZERO is needed.
GCC/Clang Built-ins
Some codebases use compiler-specific built-ins:
#define BUILD_BUG_ON_ZERO(e) \
((int)(sizeof(struct { int:-!!(e); })))
typeof-based Variants
More recent Linux kernel code sometimes uses typeof for better type safety:
#define BUILD_BUG_ON_ZERO(e) \
((typeof((long)(0)))(sizeof(struct { int:-!!(e); })))
Conclusion
The :-!! construct in Linux kernel macros represents an elegant solution to compile-time assertions that need to produce a valid result. By combining logical double negation, arithmetic negation, and bitfield specification, it creates a mechanism that:
- Fails at compile time when conditions are true
- Returns a valid zero value when conditions are false
- Works in restricted contexts like structure initializers
- Requires no runtime overhead
This technique, while appearing complex at first glance, is a testament to the power and flexibility of C’s preprocessing and type system. Despite the availability of modern alternatives like static_assert, the :-!! construct remains valuable in specific contexts where compile-time evaluation with side-effect-free results is required.
The Linux kernel’s extensive use of this macro demonstrates how low-level programming can leverage language features in unexpected ways to create robust, compile-time safety mechanisms.