chm*_*rli 1627 c linux macros linux-kernel
我在/usr/include/linux/kernel.h中碰到了这个奇怪的宏代码:
/* 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); }))
Run Code Online (Sandbox Code Playgroud)
怎么:-!!办?
Joh*_*lla 1655
实际上,这是一种检查表达式e是否可以被评估为0的方法,如果不是,则检查构建是否失败.
这个宏有点名不副实; 它应该是更像的东西BUILD_BUG_OR_ZERO,而不是...ON_ZERO.(偶尔会讨论这是否是一个令人困惑的名字.)
你应该读这样的表达式:
sizeof(struct { int: -!!(e); }))
Run Code Online (Sandbox Code Playgroud)
(e):计算表达式e.
!!(e):逻辑否定两次:0if e == 0; 否则1.
-!!(e):数字否定第2步中的表达式:0如果是0; 否则-1.
struct{int: -!!(0);} --> struct{int: 0;}:如果它为零,那么我们声明一个结构,其中包含一个宽度为零的匿名整数位域.一切都很好,我们正常进行.
struct{int: -!!(1);} --> struct{int: -1;}:另一方面,如果它不是零,那么它将是一些负数.声明任何具有负宽度的位域是编译错误.
因此,我们要么结束一个结构中宽度为0的位域,这很好,要么是负宽度的位域,这是一个编译错误.然后我们取sizeof这个字段,所以我们得到一个size_t具有适当宽度的(在零为零的情况下e为零).
有人问:为什么不用一个assert?
keithmo的答案在这里有一个很好的回应:
这些宏实现了编译时测试,而assert()是一个运行时测试.
非常正确.您不希望在运行时检测到内核中可能早先发现的问题!它是操作系统的关键部分.无论在何种程度上,在编译时都可以检测到问题,那就更好了.
Dav*_*nan 252
这:是一个位域.至于那!!,这是逻辑上的双重否定,因此返回0虚假或1真实.这-是一个减号,即算术否定.
这只是让编译器对无效输入进行barf的技巧.
考虑BUILD_BUG_ON_ZERO.当-!!(e)计算为负值时,会产生编译错误.否则-!!(e)求值为0,0宽度位域的大小为0.因此宏的size_t值为0.
在我看来,这个名称很弱,因为当输入不为零时,构建实际上会失败.
BUILD_BUG_ON_NULL非常相似,但产生一个指针而不是一个int.
Dan*_*tos 50
好吧,我很惊讶没有提到这种语法的替代方案.另一种常见(但更旧)的机制是调用未定义的函数,如果断言正确,则依赖优化器编译函数调用.
#define MY_COMPILETIME_ASSERT(test) \
do { \
extern void you_did_something_bad(void); \
if (!(test)) \
you_did_something_bad(void); \
} while (0)
Run Code Online (Sandbox Code Playgroud)
虽然这种机制有效(只要启用了优化),但它的缺点是在链接之前不报告错误,此时它无法找到函数you_did_something_bad()的定义.这就是为什么内核开发人员开始使用像负大小的位字段宽度和负大小的数组(后者在GCC 4.4中停止破坏构建)这样的技巧.
为了满足编译时断言的需要,GCC 4.3引入了error函数属性,允许您扩展这个旧概念,但生成编译时错误,并带有您选择的消息 - 不再是神秘的"负大小的数组" "错误信息!
#define MAKE_SURE_THIS_IS_FIVE(number) \
do { \
extern void this_isnt_five(void) __attribute__((error( \
"I asked for five and you gave me " #number))); \
if ((number) != 5) \
this_isnt_five(); \
} while (0)
Run Code Online (Sandbox Code Playgroud)
实际上,从Linux 3.9开始,我们现在有了一个compiletime_assert使用此功能的宏,并且大多数宏bug.h都已相应更新.但是,此宏不能用作初始化程序.但是,使用by 语句表达式(另一个GCC C-extension),你可以!
#define ANY_NUMBER_BUT_FIVE(number) \
({ \
typeof(number) n = (number); \
extern void this_number_is_five(void) __attribute__(( \
error("I told you not to give me a five!"))); \
if (n == 5) \
this_number_is_five(); \
n; \
})
Run Code Online (Sandbox Code Playgroud)
这个宏将精确评估它的参数一次(如果它有副作用)并创建一个编译时错误,上面写着"我告诉过你不要给我五个!" 如果表达式求值为5或不是编译时常量.
那么为什么我们不使用这个而不是负大小的位域?唉,目前使用语句表达式有很多限制,包括它们用作常量初始化器(用于枚举常量,位域宽度等),即使语句表达式完全不变(即可以完全评估)在编译时,否则通过__builtin_constant_p()测试).此外,它们不能在功能体外使用.
希望GCC能够尽快修改这些缺点,并允许将常量语句表达式用作常量初始化器.这里的挑战是定义什么是合法常量表达式的语言规范.C++ 11为这种类型或事物添加了constexpr关键字,但C11中没有对应关系.虽然C11确实得到了静态断言,这将解决部分问题,但它不能解决所有这些缺点.因此,我希望gcc可以通过-std = gnuc99&-std = gnuc11或者其他类似的东西将constexpr功能作为扩展提供,并允许它在语句表达式et上使用.人.
Mat*_*ips 35
0如果条件为假,则创建一个大小的位域,如果条件为真/非零,则创建一个size -1(-!!1)位域.在前一种情况下,没有错误,并且使用int成员初始化struct.在后一种情况下,存在编译错误(当然没有-1创建大小位域的事情).
| 归档时间: |
|
| 查看次数: |
179410 次 |
| 最近记录: |