物件導向 (OO) 是設計思維,而非語言語法。C 雖無 class、virtual、inheritance 關鍵字,但透過結構體 + 函數指標 + 封裝慣例,完整實現封裝、繼承、多型三大支柱。Unix「一切皆檔案」與 Linux 核心 kobject/subsys 體系,正是 C 語言 OO 設計的教科書級範例。
// 介面定義 (基類)
struct FileOps {
ssize_t (*read) (void *self, void *buf, size_t len);
ssize_t (*write) (void *self, const void *buf, size_t len);
int (*close) (void *self);
off_t (*lseek) (void *self, off_t offset, int whence);
};
// 具體實現 (衍生類:普通檔案)
struct RegularFile {
const struct FileOps *ops; // 指向 vtable
int fd; // 封裝狀態
char *path;
};
static ssize_t reg_read(void *self, void *buf, size_t len) {
struct RegularFile *f = (struct RegularFile*)self;
return read(f->fd, buf, len);
}
static ssize_t reg_write(void *self, const void *buf, size_t len) {
struct RegularFile *f = (struct RegularFile*)self;
return write(f->fd, buf, len);
}
static int reg_close(void *self) {
struct RegularFile *f = (struct RegularFile*)self;
int ret = close(f->fd);
free(f->path);
free(f);
return ret;
}
// vtable 實例 (唯讀,共享)
static const struct FileOps regular_ops = {
.read = reg_read,
.write = reg_write,
.close = reg_close,
.lseek = (off_t(*)(void*,off_t,int))lseek // 直接用系統呼叫
};
// 建構函數
struct RegularFile *RegularFile_Open(const char *path, int flags, mode_t mode)
{
struct RegularFile *f = calloc(1, sizeof(*f));
if (!f) return NULL;
f->fd = open(path, flags, mode);
if (f->fd < 0) { free(f); return NULL; }
f->path = strdup(path);
f->ops = ®ular_ops;
return f;
}
// 使用端:多型呼叫
void CopyFile(struct FileOps *src, struct FileOps *dst, size_t n)
{
char buf[4096];
while (n > 0) {
size_t chunk = n < sizeof(buf) ? n : sizeof(buf);
ssize_t r = src->read(src, buf, chunk);
if (r <= 0) break;
ssize_t w = dst->write(dst, buf, r);
if (w != r) break;
n -= r;
}
}
kobject — 所有核心物件的基類
struct kobject {
const char *name;
struct list_head entry; // 父子/兄弟鏈表
struct kobject *parent; // 繼承關係 (目錄樹)
struct kset *kset; // 群組 (類別)
struct kref kref; // 參考計數 (共享所有權)
const struct kobj_type *ktype; // 虛函數表 (release, sysfs_ops)
struct kernfs_node *sd; // sysfs 節點
};
kobject_init/kobject_add/kobject_put 管理生命週期。struct device、struct bus_type、struct class 皆以 struct kobject 為首欄位 (C 語言「結構體首欄位地址等於結構體地址」特性),實現 Embedding 型繼承。kobj_type->release()、kobj_type->sysfs_ops->show/store() 依具體類型分派。file_operations — 字元裝置驅動的介面契約
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
// ... 更多操作
};
驅動開發者填寫此結構體註冊 cdev_add,用戶空間 open/read/write/ioctl 經 VFS 分派至對應函數指標 — 純 C 實現的介面導向程式設計。
sysfs — 核心物件模型的可視化映射/sys 目錄樹即為 kobject 層級關係的直接投影:
/sys/devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
├── size
├── stat
├── queue/
│ ├── scheduler
│ └── nr_requests
└── holder/
每個目錄對應一個 kobject,屬性檔案對應 kobj_attribute (show/store 回呼),完美展示「物件圖 → 檔案系統」的雙向映射。
| 模式 | C 實現技術 | 關鍵點 |
|---|---|---|
| 封裝 | 不透明指標 (Opaque Pointer) + 存取函數 | .h 只宣告 struct Foo;,.c 定義完整結構;提供 Foo_Create/Destroy/Get/Set |
| 單一繼承 | 結構體首欄位嵌入基類 | struct Derived { Base base; ...; },(Base*)derived == derived |
| 多重繼承 | 多個基類嵌入 + container_of 宏 | container_of(ptr, type, member) 從成員指標反推物件首址 |
| 虛函數/多型 | 函數指標表 (vtable) + 首欄位指向 vtable | vtable 為 const 全域單例,建構時指派 |
| 建構/解構 | Type_Init(Type*) / Type_Fini(Type*) + malloc/free | 分離記憶體配置與初始化,支援靜態/動態/陣列分配 |
| 介面/抽象類 | 純虛函數表 (全為函數指標的結構體) | 無資料成員,僅定義契約;實作者嵌入並填寫 |
| 參考計數 | kref / atomic_t + Get/Put | 最後一次 Put 呼叫 release 回呼釋放資源 |
sd-bus、sd-event、Unit 物件模型,大量使用 refcount、vtable、container_of。uv_handle_t 基類,uv_tcp_t/uv_timer_t/uv_fs_t 衍生,事件迴圈多型分派。AVClass 為核心物件模型,AVCodec/AVFormatContext/AVFilterContext 皆繼承,支援選項系統、日誌分類、版本檢查。Object 基類 (GObject 風格),DeviceClass/BusClass、屬性系統、信號/槽、動態鑄造。C 語言缺乏語法糖,反而迫使開發者直面物件模型本質:資料佈局、函數分派、生命週期、所有權轉移。Linux 核心、systemd、libuv、FFmpeg 證明:純 C 即可構建百萬行級、高效能、高可維護性的物件導向系統。學會在 C 中手寫 vtable、嵌入繼承、container_of、參考計數,不僅能讀懂頂級開源專案核心架構,更能在嵌入式、核心模組、高頻交易、遊戲引擎等「不能用 C++/Rust」的場景自信落地 OO 設計。