断言是非常常见的,其语义就是判断某个条件,如果不为真,就执行一段非常规的动作,一般为程序立马结束运行。
断言分为动态和静态的。动态断言也叫运行时断言,即在程序运行的使用,由比较指令来判断条件;而静态断言是在代码的编译过程中,通过编译器对常量表达式的计算来判断条件成立与否的,即一般不会生成额外的代码。
#define static_assert(cond) \
do { if (!cond) (void)sizeof(char[-1]); } while(0)
cond
必须要为常量表达式,即非运行时就可以确定结果的表达式。
当断言失败时将出现下面的信息:
../../include/utils/compiler.h:247:20: error: size of unnamed array is negative
__attribuite__((error(message)))
来描述一个不存在的函数,当编译时发现调用了此函数就打印 message
并终止编译,一般来说,函数的存在与否必须要等到链接时才可知的,但GNU的编译器支持这个特性。比如:#define static_assert(cond) \
do { if (!cond) { extern void __static_assert_func(void) ___attribute__("static assert failed"); __static_assert_func(); } } while(0)
同样 cond
必须要为常量表达式,即非运行时就可以确定结果的表达式。
当断言失败时将出现下面的信息:
../../include/utils/compiler.h:247:20: error: call to ‘__static_assert_func’ declared with attribute error: static assert failed
内核使用 BUILD_BUG_ON_XXX()
来作为前缀定义一系列的静态断言,比如在定义一个可以配置的哈希表时,为了加快哈希桶的定位,一般都是用 2^n
大小的桶,并用哈希值与其掩码来计算桶下标,即: hash_value & (bucket_size -1)
。为了防止非 2^n
大小的哈希表出现,定义 BUILD_BUG_ON_NOT_POWER_OF_2()
来快速判断大小。
这些宏都最终都是由下列宏实现 :
#define GCC_VERSION (__GNUC__ * 10000 \
+ __GNUC_MINOR__ * 100 \
+ __GNUC_PATCHLEVEL__)
#if GCC_VERSION >= 40800
# define __compiletime_error(message) __attribute__((error(message)))
#endif
#ifndef __compiletime_error
# define __compiletime_error(message)
#endif
# define __compiletime_error_fallback(condition) \
do { ((void)sizeof(char[1 - 2 * condition])); } while (0)
#if defined(__OPTIMIZE__)
# define __compiletime_assert(condition, msg, prefix, suffix) \
do { \
bool __cond = !(condition); \
extern void prefix ## suffix(void) __compiletime_error(msg); \
if (__cond) \
prefix ## suffix(); \
__compiletime_error_fallback(__cond); \
} while (0)
#else
# define __compiletime_assert(condition, msg, prefix, suffix) \
__compiletime_error_fallback(!(condition))
#endif
#define _compiletime_assert(condition, msg, prefix, suffix) \
__compiletime_assert(condition, msg, prefix, suffix)
#define compiletime_assert(condition, msg) \
_compiletime_assert(condition, msg, __compiletime_assert_, __LINE__)
#define BUILD_BUG_ON(condition) \
BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)
#define BUILD_BUG_ON_NOT_POWER_OF_2(n) \
BUILD_BUG_ON((n) == 0 || (((n) & ((n) - 1)) != 0))
上面的定义做了一些改动,你可以直接用于自己的应用层程序。 __OPTIMIZE__
表示在使用 -O
并且 n
大于0时,编译器会定义此宏,并且要GCC编译器特性来断言时,必须要使用较新的版本,我在 macOS
上测试时,GCC就不支持 __attribuite__((error(message)))
这个扩展。
刚才我们提示过,动态的断言将使用指令,完成断言,那么聪明的内核在条件不成立时执行一个CPU不认识的指令来触发一个指令异常,并被异常子系统捕获,最终会执行宕机处理。
#define BUG() __asm__ __volatile__("ud2\n")
我们在应用层编程时,直接使用 assert.h
头文件中的 assert(cond)
就可以了。
内核的断言有许多的代码技巧,你在掌握了基本原理后,可以在所有的 bug.h
中学习动态断言,以及在 compiler.h
和 build_bug.h
中学习静态断言。