世界を動かす技術を、日本語で。

C言語におけるオブジェクト指向デザインパターンとカーネル開発

2025年8月26日原文(oshub.org)

概要

  • OS開発の自由度 を活かし、独自の設計パターンを実践
  • C言語でのオブジェクト指向設計 による柔軟なカーネルサービス実装
  • vtable(関数ポインタ構造体) で動的な振る舞い変更を実現
  • 一貫したインターフェース により、異なるサービスやスケジューラの切替えが容易
  • デメリットと利点 を踏まえたカーネル開発の楽しさを強調

オリジナルOS開発におけるスケジューラ操作とvtable活用

  • OS開発(osdev)の魅力 は、他者との協業やリリース、セキュリティ維持の制約から解放される自由度
  • 孤独な開発環境 が、ユニークなプログラミングパターンの実験を可能に
  • Linuxカーネルの記事 (“Object-oriented design patterns in the kernel”)をきっかけに、C言語でもオブジェクト指向設計を導入
  • カプセル化・モジュール性・拡張性 をCで実現するため、関数ポインタを持つ構造体(vtable)を活用

vtableによるインターフェース設計

  • vtable(関数ポインタ構造体) でオブジェクトのインターフェースを定義
    • 例:struct device_ops に start/stop の関数ポインタを定義
  • 各デバイス構造体がvtableへの参照を保持
    • 例:struct deviceconst struct device_ops *ops を持つ
  • 異なるデバイスが同一APIで異なる挙動
    • 例:ネットワークデバイスとディスクデバイスで start/stop の実装が異なる

vtableの動的切替え

  • vtableの差し替え で、ランタイム中に振る舞いを変更可能
  • 呼び出し側コードに変更不要、柔軟な挙動進化を実現
  • 適切な同期処理 で安全な動的拡張

サービス管理への応用

  • OS内のサービス(ネットワーク管理、ウィンドウサーバ等) もvtableで統一的に管理
  • start/restart/stop の操作を持つサービスインターフェース
  • サービスごとの差異を吸収し、端末からの一貫操作を実現

スケジューラの実装

  • 複数のスケジューリング戦略(ラウンドロビン、優先度順等) をvtableで切替え可能
  • 必要な操作(yield, block, add, next) のみをインターフェース化
  • カーネル全体のコードは変更せず、戦略の差し替えが可能

Linuxの例:file_operations

  • Linuxカーネルの struct file_operations もvtableパターンの典型例
  • Unix/Plan 9の「全てはファイル」哲学 を支える統一インターフェース
    • ソケット、デバイス、テキストファイル全てが同一のread/write API

カーネルモジュールとの組み合わせ

  • カーネルモジュールとvtableの相性の良さ
    • vtable差し替えで、動的なドライバやフックの追加が可能
    • システム再起動や再コンパイル不要で拡張性向上

デメリットと利点

  • C言語でのvtable利用は冗長なシンタックス
    • 毎回 object->ops->start(object) のような呼び出しが必要
    • C++のような暗黙のthisがなく、関数シグネチャも冗長化
  • 明示的なthis渡しの利点
    • 関数の依存関係が明確になり、カーネルコードの結合度が可視化

まとめ

  • vtable活用により、カーネルコードの柔軟性と一貫性を両立
  • ランタイムでの挙動変更・新機能追加が容易
  • C言語の新たな活用法を発見し、OS開発の実験場としての楽しさを実感

参考文献

  • xine project でのvtableとプライベート変数の活用事例

Hackerたちの意見

記事では、LinuxカーネルがCで書かれているにもかかわらず、ポリモーフィズムを実現するために構造体内で関数ポインタを使ってオブジェクト指向の原則を取り入れていることが説明されています。この技術はオブジェクト指向プログラミングよりも前から存在しています。これは抽象データ型またはデータ抽象と呼ばれています。データ抽象とオブジェクト指向プログラミングの大きな違いは、抽象データ型では関数を未実装のままにできるのに対し、OOPでは関数を常に実装する必要があることです。オブジェクト指向プログラミングでオプションの関数を持つ最も理にかなった方法は、各オプション関数のために追加のクラスを作成し、基本クラスとともに多重継承で実装することだと思います。その後、オプション関数を使う前に、オブジェクトが追加クラスのインスタンスかどうかをランタイムでチェックする必要があります。抽象データ型では、関数ポインタが存在するかどうかを確認するために、単純なNULLチェックを行うだけで済みます。

大体同意しますし、Cでもこれらのパターンを使っていますが、クラシックなOOPのためのデフォルトまたはスタブ実装を基本に持つという一般的なアプローチを無視しています。また、よりモダンなOOPやコンセプトスタイルの言語では、インターフェースを使う選択肢もあります。インターフェース型にキャストすることで、実際に呼び出す必要があるAPIのサブセットだけを要求できます。Goはその良い例で、実際に関数ポインタのテーブルからランタイムでルックアップを行っています。

抽象データ型の概念は、コンパイラ設計の時代に実際に存在したアイデアです。「コンパイラ設計はオブジェクト指向プログラミングよりも前から存在している」と言ってもいいでしょう。リードで説明されている技術は、オブジェクト指向プログラミングの構造を実装するために使われています。実際、コンパイラ設計の多くの機能がその背後にあります。ソースとして、当時MetroWerksでCを使ってこのパターンや他のものを使ってMacOSのウィンドウフレームワークを書きました。

SmalltalkやObjective-Cでは、オブジェクトインスタンスがメッセージに応答するかどうかをランタイムでチェックします。これが元々のOOPのやり方です。OOPが過度にクラス中心のC++やJavaのデザインパターンによって腐敗したのは悲しいことです。

この技術はオブジェクト指向プログラミングよりも前から存在しています。OOPは、前から存在していたパターンやパラダイムの形式化だと言った方がいいでしょう。

コンポジットパターンが使えるなら、継承は必要ないよ。 class DefaultTask { } class SpecialTask { } class UsedItem { UsedItem() { _task = new SpecialTask() } void DoIt() { _task.DoIt() } } PythonってOOP言語なの?self / this / オブジェクトポインタは、Cスタイルのオブジェクト指向やデータ抽象を使うのと同じように渡さなきゃいけないよね。

JavaやC#のようなほとんどのOOP言語でCでやったことをそのままできるよ。だって、今はラムダがあるからね。ラムダはただの関数ポインタなんだ。インスタンス変数(または静的変数)に割り当てることもできるよ。(ごめん、Javaが追いつくのに10年以上かかったし、Sun Microsystemsは昔ラムダをJavaに追加しようとしたMicrosoftを訴えたんだ。それで、匿名内部クラスが十分に良い代替品だって主張するホワイトペーパーまで書いたんだよ - 笑わないでね)

オブジェクトを毎回明示的に渡さなければならないのは、特にC++と比べると不格好に感じます。個人的には暗黙のthisは好きじゃないです。これはクラスメソッドではなく、thisインスタンスを渡していることになりますからね。また、明示的なthisは、変数がインスタンス変数なのかグローバル変数なのか、どこから来たのかを知らないという問題を解消します。

また、明示的なthisは、変数がインスタンス変数なのかグローバル変数なのか、どこから来たのかを知らないという問題を解消します。人々は通常、メンバー変数のために何らかの命名規則を使います。例えば、mFoo、m_Foo、m_foo、foo_などですから、これは問題ではありません。foo_の方がthis->fooよりもずっと簡潔だと思います。また、C++では本当に必要なら明示的なthisを使うこともできますよ。

...そしてC++はC++23で明示的なthisパラメータ(thisを推測する)を追加しました。

著者が言ってるのはこれだと思う:object->ops->start(object) ここでは、明示的であるだけでなく、オブジェクトを2回指定する必要があるんだよね(1回目はVtableを解決するため、2回目はそのオブジェクトをステートレスなCメソッドに渡すため)。

「this」はC++で予約語だから、グローバル変数になる心配はないよ。ただ、Cの抽象データ型(ADT)みたいに、明示的にthisポインタを渡すのが好きなんだ。thisポインタが必要ない関数は、開発者が関数をstaticにするのを忘れたり、全ての関数アクセスを::演算子を使うように書き換えたくない時に、うっかり渡されることがないからね。

Hacker Newsで議論の続きを見る