C语言中 ':-!!' 的含义解析

C语言中 ‘:-!!’ 的含义解析

技术背景

在Linux内核代码(如 /usr/include/linux/kernel.h/usr/include/linux/build_bug.h)中,存在如下宏定义:

1
2
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))

这些宏的作用是在编译时检查表达式 e 的值,如果 e 不为0,则会导致编译错误。那么其中的 :-!! 具体是什么意思呢?

实现步骤

1. 计算表达式 e 的值

首先,对传入宏的表达式 e 进行计算。

2. 逻辑双重取反 !!(e)

使用逻辑非运算符 !e 进行两次取反。在C语言中,逻辑非运算符会将非零值转换为0,将0转换为1。因此,!!(e) 的结果是:如果 e 为0,则 !!(e) 为0;如果 e 不为0,则 !!(e) 为1。

3. 算术取反 -!!(e)

!!(e) 的结果进行算术取反。如果 !!(e) 为0,则 -!!(e) 为0;如果 !!(e) 为1,则 -!!(e) 为 -1。

4. 位域声明

在宏定义中,使用 struct { int: -!!(e); } 声明一个匿名的整数位域。如果 -!!(e) 为0,则声明一个宽度为0的位域;如果 -!!(e) 为 -1,则声明一个宽度为负数的位域。

5. 编译检查

在C语言中,声明宽度为负数的位域是非法的,会导致编译错误。因此,如果 e 不为0,则会触发编译错误;如果 e 为0,则一切正常,并且可以获取该结构体的大小(通常为0)。

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

// 定义宏 BUILD_BUG_ON_ZERO
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))

int main() {
// 当 e 为 0 时,编译正常
size_t result1 = BUILD_BUG_ON_ZERO(0);
printf("result1: %zu\n", result1);

// 当 e 不为 0 时,编译错误
// size_t result2 = BUILD_BUG_ON_ZERO(1);

return 0;
}

最佳实践

替代方案

在现代C标准(如C11)中,引入了 _Static_assert 关键字,可以用于实现编译时检查。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stddef.h>

// 使用 _Static_assert 进行编译时检查
#define CHECK_ZERO(e) _Static_assert((e) == 0, "Expression must be zero!")

int main() {
// 当 e 为 0 时,编译正常
CHECK_ZERO(0);

// 当 e 不为 0 时,编译错误
// CHECK_ZERO(1);

return 0;
}

注意事项

  • BUILD_BUG_ON_ZEROBUILD_BUG_ON_NULL 宏只能用于编译时可计算的表达式。
  • 在使用这些宏时,要确保代码的可移植性,因为某些编译器可能对负数宽度的位域有不同的处理。

常见问题

为什么不使用 assert

assert 是一个运行时检查机制,它在程序运行时检查条件是否为真。而 BUILD_BUG_ON_ZEROBUILD_BUG_ON_NULL 是编译时检查机制,它们可以在编译阶段就发现问题,避免在运行时出现潜在的错误。

sizeof(struct { int:0; }) 是否符合标准?

在标准C中,struct { int:0; } 的行为是未定义的。但是,GCC编译器支持这种用法,并将其视为大小为0的结构体。因此,在使用这些宏时,要注意代码的可移植性。


C语言中 ':-!!' 的含义解析
https://119291.xyz/posts/2025-04-23.meaning-of-colon-exclamation-exclamation-in-c/
作者
ww
发布于
2025年4月23日
许可协议