Linuxカーネルで見たc言語の小技:BUILD_BUG_ONマクロ

LinuxカーネルにあるBUILD_BUG_ONマクロ。名前からやりたいことは十分に分かるんだけど、これはどういう仕組みなのかというところ。

使っている例としてarch/x86/kernel/head64.cにあるx86_64_start_kernel()を見てみるとこんな感じ。

140 asmlinkage void __init x86_64_start_kernel(char * real_mode_data)
141 {
142         int i;
143 
144         /*
145          * Build-time sanity checks on the kernel image and module
146          * area mappings. (these are purely build-time and produce no code)
147          */
148         BUILD_BUG_ON(MODULES_VADDR < __START_KERNEL_map);
149         BUILD_BUG_ON(MODULES_VADDR - __START_KERNEL_map < KERNEL_IMAGE_SIZE);
150         BUILD_BUG_ON(MODULES_LEN + KERNEL_IMAGE_SIZE > 2*PUD_SIZE);
151         BUILD_BUG_ON((__START_KERNEL_map & ~PMD_MASK) != 0);
152         BUILD_BUG_ON((MODULES_VADDR & ~PMD_MASK) != 0);
153         BUILD_BUG_ON(!(MODULES_VADDR > __START_KERNEL));
154         BUILD_BUG_ON(!(((MODULES_END - 1) & PGDIR_MASK) ==
155                                 (__START_KERNEL & PGDIR_MASK)));
156         BUILD_BUG_ON(__fix_to_virt(__end_of_fixed_addresses) <= MODULES_END);

BUILD_BUG_ONマクロの実装はinclude/linux/genl_magic_func.hにあり、渡された条件をBUILD_BUG_ON_ZEROマクロにそのまま渡すだけ。

134 #ifndef BUILD_BUG_ON
135 /* Force a compilation error if condition is true */
136 #define BUILD_BUG_ON(condition) ((void)BUILD_BUG_ON_ZERO(condition))
137 /* Force a compilation error if condition is true, but also produce a
138    result (of value 0 and type size_t), so the expression can be used
139    e.g. in a structure initializer (or where-ever else comma expressions
140    aren't permitted). */
141 #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
142 #define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))
143 #endif

ということで、BUILD_BUG_ON_ZEROを見ると、構造体をsizeofするという感じですね。
この構造体はメンバ変数にビットフィールドを使っていて評価結果が1の場合、ビットフィールドに指定する数値が-1となりコンパイラエラーになるという算段です。
ここでsizeofをしているのは単に構造体の定義をここでしたいからということでしょうね。

実際に簡単にテストをしてみる。まずはこんなファイルを。

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

int
main(int argc, char **argv)
{
        BUILD_BUG_ON(1 == 1);
        return 0;
}

プリプロセッサだけ展開してみる。

masami@saga:~$ gcc -E test.c
# 1 "test.c"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "test.c"



int
main(int argc, char **argv)
{
 ((void)(sizeof(struct { int:-!!(1 == 1); })));
 return 0;
}

この場合は(1 == 1)の結果は1になるのでint:-1とされてコンパイルエラーになる

実際にコンパイルしてみる。

masami@saga:~$ gcc test.c
test.c: In function ‘main’:
test.c:2:45: error: negative width in bit-field ‘<anonymous>’
 #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
                                             ^
test.c:1:40: note: in expansion of macro ‘BUILD_BUG_ON_ZERO’
 #define BUILD_BUG_ON(condition) ((void)BUILD_BUG_ON_ZERO(condition))
                                        ^
test.c:7:2: note: in expansion of macro ‘BUILD_BUG_ON’
  BUILD_BUG_ON(1 == 1);
  ^

(´-`).。oO(よく考えるもんだな~

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)

リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)