リーダブル低レイヤコード
ここでは、低レイヤのコードを書くに当たって、経験上大事にしていることを書いています。 内容はあくまで個人的なものですので、読み物として参考程度にしてください。
内容は適宜追加していきます。
構造体のstatic assertは確認すべき箇所を書こう
struct Hoge {
uintptr_t a;
size_t b;
}
static_assert(sizeof(struct Hoge) == 16, "The size of hoge was changed!");
上のコードでは、構造体のサイズが変わった際にコンパイルエラーになる。 この手のAssertionは低レイヤではよく見るが、この情報だけでは構造体のサイズが変わると何が不味いのかが分からない。
一つ考えられる事として、uintptr_t
やsize_t
のサイズが処理系やCPUアーキテクチャが変わった際に対処が必要な場合がある。
本来ならばuint32_t
やuint64_t
などサイズの決まった型を使うべきだが、デバイスやインターフェイスの仕様書などに合わせるためにこのように書いている場合がある。
そのため他の環境に移植した際は注意深く挙動を見るべきということでstatic_assert
があるのかもしれない。
もう一つの考えられる事として、構造体が別の場所にも定義されていて、データの受け渡しに使われているのかもしれない。 この場合、一方を書き換えたらもう一方も書き換える必要もある。 さらに構造体の定義自体だけの問題ではなく、構造体の確保自体もサイズに依存している場合がある。 例えば、以下のように確保したメモリをキャストしていたり、リンカスクリプトにサイズが直書きされている場合である。
struct Hoge *message_buffer = NULL;
void init_struct(void) {
auto *page = alloc_page(GFP_KERNEL);
if (!page) {
/* ... */
}
message_buffer = page_address(page);
for(unsigned int i = 0; i < 256; i++) {
message_buffer[i] = /* ... */;
}
}
複数箇所修正しないといけない場合、コードをよく知らない人は一箇所だけ修正して満足してしまうことが多い。
そのため、構造体のサイズやメンバのオフセットに対して、static_assert
を書く場合は確認すべき箇所をコメントなどでリストとしておくと良い。
また、サイズやオフセット依存のコードを書いた場合は、それらに追記するべきである。
// If you modified the struct, you should check `payload/boot.h`.
// And `init_struct` assumes that `sizeof(Hoge) * 256 <= PAGE_SIZE`
static_assert(sizeof(struct Hoge) == 16, "The size of hoge was changed!");
do whileとcontinueの使い方には気をつけよう
別に低レイヤに限らないが、C言語でdo { ... } while(0);
を使う場合がある。
マクロで変数のスコープを区切るためにカーネルなどで使用されるが、その中でcontinue
を使用すると意図しない挙動になる場合がある。
例えば以下のようなマクロがあるとする。
#define HOGE(a) do { int b = foo(a); if (b) { continue; } } while(0);
マクロの良し悪しはともかく、HOGE
ではfoo
がゼロを返すまで呼ぶことを期待しているように見える。
もしくは書いた人はそれを期待して書いたのかもしれない。
しかしこれは期待どおりに動かない。
なぜならcontinue
文は「ブロックの最初に戻る」のではなく、「ブロックの一番最後に飛ぶ」文であるからである。
すなわち、ここでのcontinue
はwhile(0)
の手前にジャンプする事を意味し、実質break
と同等の働きをすることになる。
continue
の挙動を理解していても、案外目が滑りがちである。
この場合は、以下のように書き直すか、ラベルの使用や別の手段(インライン関数など)を検討すべきである。
#define HOGE(a) while(1) { int b = foo(a); if (b) { continue; } break;};