リーダブル低レイヤコード

ここでは、低レイヤのコードを書くに当たって、経験上大事にしていることを書いています。 内容はあくまで個人的なものですので、読み物として参考程度にしてください。

内容は適宜追加していきます。

構造体のstatic assertは確認すべき箇所を書こう

struct Hoge {
    uintptr_t a;
    size_t b;
}

static_assert(sizeof(struct Hoge) == 16, "The size of hoge was changed!");

上のコードでは、構造体のサイズが変わった際にコンパイルエラーになる。 この手のAssertionは低レイヤではよく見るが、この情報だけでは構造体のサイズが変わると何が不味いのかが分からない。

一つ考えられる事として、uintptr_tsize_tのサイズが処理系やCPUアーキテクチャが変わった際に対処が必要な場合がある。 本来ならばuint32_tuint64_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文は「ブロックの最初に戻る」のではなく、「ブロックの一番最後に飛ぶ」文であるからである。 すなわち、ここでのcontinuewhile(0)の手前にジャンプする事を意味し、実質breakと同等の働きをすることになる。

continueの挙動を理解していても、案外目が滑りがちである。

この場合は、以下のように書き直すか、ラベルの使用や別の手段(インライン関数など)を検討すべきである。

#define HOGE(a) while(1) { int b = foo(a); if (b) { continue; } break;};