c-oop.md 40 KB


tags: [concept, c, oop, embedded, linux-kernel] type: guide created: 2026-07-23

updated: 2026-07-25

C语言面向对象编程完整指南

一句话本质:C 没有 classprivate、继承或虚函数;C-OOP 是用 struct、函数、首成员嵌入和函数指针表手动组织出类似面向对象的结构。C++ 编译器常用相似的底层布局实现对象模型,但 C++ 标准并不要求“先翻译成 C”。


目录

  1. [[#一、开胃菜:5 分钟看到你的第一个 C 类]]
  2. [[#二、封装:创建你自己的 C 类]]
  3. [[#三、继承:复用公共字段]]
  4. [[#四、多态:同一个接口,不同的行为]]
  5. [[#五、向下转型:container_of 原理]]
  6. [[#六、四层架构:工业级项目怎么组织]]
  7. [[#七、Linux 内核中的 C-OOP]]
  8. [[#八、速查卡(打印贴墙用)]]

一、开胃菜:5 分钟看到你的第一个 C 类

先看三个核心对应关系,然后直接上手:

// C++ 概念          →  C 实现
// class Student     →  Student.h + Student.c
// private int id;   →  struct Student { int id; }; 写在 .c 里
// public void set() →  Student.h 中声明的函数
// this->id          →  me->id(me 是显式第一个参数)

完整的 Student 类(按 3 个文件复制后即可编译)

在同一个目录新建下列三个文件。这里使用 Student_create / Student_destroy,这是不透明类型的正确用法:调用方不知道 struct Student 的大小,因而不能在栈上写 Student s;

// ==================== Student.h ====================
#ifndef STUDENT_H
#define STUDENT_H

typedef struct Student Student;

Student *Student_create(int id, int grade);
void Student_destroy(Student *self);
int Student_set_id(Student *self, int id);
int Student_get_id(const Student *self);
int Student_set_grade(Student *self, int grade);
int Student_get_grade(const Student *self);
void Student_print(const Student *self);

#endif
// ==================== Student.c ====================
#include "Student.h"
#include <stdio.h>
#include <stdlib.h>

struct Student { int id; int grade; };

static int Student_is_valid_grade(int grade) { return grade >= 0 && grade <= 100; }

Student *Student_create(int id, int grade)
{
    Student *self;
    if (id < 0 || !Student_is_valid_grade(grade)) return NULL;
    self = malloc(sizeof(*self));
    if (self == NULL) return NULL;
    self->id = id;
    self->grade = grade;
    return self;
}
void Student_destroy(Student *self) { free(self); }
int Student_set_id(Student *self, int id)
{
    if (self == NULL || id < 0) return -1;
    self->id = id;
    return 0;
}
int Student_get_id(const Student *self) { return self == NULL ? -1 : self->id; }
int Student_set_grade(Student *self, int grade)
{
    if (self == NULL || !Student_is_valid_grade(grade)) return -1;
    self->grade = grade;
    return 0;
}
int Student_get_grade(const Student *self) { return self == NULL ? -1 : self->grade; }
void Student_print(const Student *self)
{
    if (self != NULL) printf("Student{id=%d, grade=%d}\n", self->id, self->grade);
}
// ==================== main.c ====================
#include "Student.h"
#include <assert.h>
#include <stddef.h>

int main(void)
{
    Student *student = Student_create(1001, 85);
    assert(student != NULL);
    assert(Student_set_grade(student, 90) == 0);
    assert(Student_set_grade(student, 101) == -1);
    Student_print(student);
    Student_destroy(student);
    return 0;
}
# 在这三个文件所在目录执行
gcc -std=c11 -Wall -Wextra -Werror -pedantic Student.c main.c -o student_test
./student_test

预期输出:Student{id=1001, grade=90}。尝试写 student->grade = 90; 会在 main.c 编译失败,因为该文件只见到不完整类型。

二、封装:创建你自己的 C 类

2.1 你遇到的问题

// 坏代码:全局变量到处可改
int led_pin = 15;            // 谁都能改 led_pin = 999
void led_on() { /* ... */ }

// 另一个文件不小心:
led_pin = 999;                // bug!找了一下午

封装要解决的就是这个问题:把数据和操作绑在一起,对外隐藏内部细节。

2.2 C 的封装三板斧

手法 C 语法 效果
结构体打包 struct 把相关字段捆在一起
信息隐藏 .h 只放声明,struct 定义放 .c 外部无法访问成员
私有化 static 修饰函数/变量 仅本.c 文件可见
命名空间 模块前缀xxx_ 不同模块函数不会撞名
生命周期 init() / deinit() 配对 规范化构造/析构

2.3 完整模板:XXX 类骨架

这是你每次创建新类都要用的模板。直接复制,替换 XXX

// ==================== XXX.h ====================
#ifndef XXX_H
#define XXX_H

#include <stdint.h>
#include <stdbool.h>

/* ---- 不透明类型(外部无法访问内部字段) ---- */
typedef struct XXX XXX;

/* ---- 构造/析构 ---- */
void XXX_init(XXX *me, int param);
void XXX_deinit(XXX *me);

/* ---- 公开方法(public) ---- */
int   XXX_getValue(const XXX *me);
void  XXX_setValue(XXX *me, int v);
bool  XXX_isReady(const XXX *me);
void  XXX_reset(XXX *me);

/* ---- 公开常量 ---- */
#define XXX_MAX_VALUE  255

#endif /* XXX_H */
// ==================== XXX.c ====================
#include "XXX.h"
#include <stdio.h>
#include <assert.h>

/* ========== private 成员变量 ========== */
struct XXX {
    int  value;
    bool ready;
};

/* ========== private 静态变量 ========== */
static int instance_count = 0;

/* ========== private 方法 ========== */
static void validate(XXX *me) {
    assert(me->value >= 0 && me->value <= XXX_MAX_VALUE);
}

/* ========== 构造 / 析构 ========== */
void XXX_init(XXX *me, int param) {
    me->value = param;
    me->ready = true;
    instance_count++;
    validate(me);
}
void XXX_deinit(XXX *me) {
    me->ready = false;
    instance_count--;
}

/* ========== 公开方法 ========== */
int  XXX_getValue(const XXX *me) { return me->value; }
void XXX_setValue(XXX *me, int v) { me->value = v; validate(me); }
bool XXX_isReady(const XXX *me)   { return me->ready; }
void XXX_reset(XXX *me)           { me->value = 0; }

2.4 实际例子:RingBuffer(环形缓冲区)

这是一个真正有用的类,体现封装的全部要点:

// ==================== RingBuffer.h ====================
#ifndef RINGBUFFER_H
#define RINGBUFFER_H

#include <stdint.h>
#include <stdbool.h>

typedef struct RingBuffer RingBuffer;

RingBuffer *RingBuffer_create(uint8_t *buf, int size);
void RingBuffer_destroy(RingBuffer *me);
bool RingBuffer_put(RingBuffer *me, uint8_t byte);
bool RingBuffer_get(RingBuffer *me, uint8_t *byte);
int  RingBuffer_count(const RingBuffer *me);
bool RingBuffer_isFull(const RingBuffer *me);
bool RingBuffer_isEmpty(const RingBuffer *me);
void RingBuffer_clear(RingBuffer *me);

#endif
// ==================== RingBuffer.c ====================
#include "RingBuffer.h"
#include <stdlib.h>

/* private 成员 */
struct RingBuffer {
    uint8_t *buf;
    int      size;
    int      head;   // 写指针
    int      tail;   // 读指针
};

/* private 方法 */
static int next_pos(int pos, int size) {
    return (pos + 1) % size;
}

/* 构造 */
RingBuffer *RingBuffer_create(uint8_t *buf, int size) {
    if (buf == NULL || size < 2) return NULL;
    RingBuffer *me = malloc(sizeof(*me));
    if (me == NULL) return NULL;
    me->buf  = buf;
    me->size = size;
    me->head = 0;
    me->tail = 0;
    return me;
}
void RingBuffer_destroy(RingBuffer *me) { free(me); }

/* 公开方法 */
bool RingBuffer_put(RingBuffer *me, uint8_t byte) {
    if (RingBuffer_isFull(me)) return false;
    me->buf[me->head] = byte;
    me->head = next_pos(me->head, me->size);
    return true;
}
bool RingBuffer_get(RingBuffer *me, uint8_t *byte) {
    if (RingBuffer_isEmpty(me)) return false;
    *byte = me->buf[me->tail];
    me->tail = next_pos(me->tail, me->size);
    return true;
}
int  RingBuffer_count(const RingBuffer *me) {
    if (me->head >= me->tail)
        return me->head - me->tail;
    return me->size - (me->tail - me->head);
}
bool RingBuffer_isFull(const RingBuffer *me) {
    return next_pos(me->head, me->size) == me->tail;
}
bool RingBuffer_isEmpty(const RingBuffer *me) {
    return me->head == me->tail;
}
void RingBuffer_clear(RingBuffer *me) {
    me->head = me->tail = 0;
}
// ==================== main.c ====================
#include "RingBuffer.h"
#include <stdio.h>

int main(void) {
    uint8_t mem[16];
    RingBuffer *rb = RingBuffer_create(mem, sizeof(mem));
    if (rb == NULL) return 1;

    RingBuffer_put(rb, 'H');
    RingBuffer_put(rb, 'i');

    uint8_t ch;
    while (RingBuffer_get(rb, &ch)) {
        putchar(ch);              // → Hi
    }
    putchar('\n');

    // rb.head = 999;             ← 编译错误!private!
    RingBuffer_destroy(rb);
    return 0;
}
gcc -std=c11 -Wall -Wextra -Werror -pedantic RingBuffer.c main.c -o ringbuffer_test
./ringbuffer_test

预期输出:Hi

2.5 对照:C++/Java 的封装 vs C

C++                         Java                        C
──────────────────────────────────────────────────────────────────
class RingBuffer {          class RingBuffer {          // RingBuffer.h
private:                    private:                    typedef struct RingBuffer RingBuffer;
    uint8_t* buf;               byte[] buf;             // RingBuffer.c
    int head, tail;             int head, tail;          struct RingBuffer { ... };
public:                     public:                     // RingBuffer.h
    void put(uint8_t b);        void put(byte b);        void RingBuffer_put(RingBuffer*, uint8_t);
private:                    private:                    // RingBuffer.c
    int nextPos(int p);         int nextPos(int p);      static int next_pos(...);
};

RingBuffer rb;              RingBuffer rb = new...;    RingBuffer rb;
rb.put('H');                rb.put('H');               RingBuffer_put(rb, 'H');
// rb.head                   // rb.head                 // rb.head → 编译错误!

三、继承:复用公共字段

3.1 你遇到的问题

struct DataPacket {
    uint32_t src;            // ← 这两个字段每个包类型都有
    uint32_t dst;            // ←
    uint32_t seq_num;
    uint8_t  payload[64];
};
struct AckPacket {
    uint32_t src;            // ← 重复!
    uint32_t dst;            // ← 重复!
    uint32_t ack_num;
    uint32_t window;
};
// 改 src 类型?要改两个结构体!

3.2 C 的"继承":结构体嵌套

核心规则:基类 struct 作为子类 struct 的第一个成员

// 基类:所有包的公共头
struct PacketHeader {
    uint32_t src;
    uint32_t dst;
    uint16_t len;
};

// 子类:数据包("继承" PacketHeader)
struct DataPacket {
    struct PacketHeader hdr;   // ★ 第一个成员 = 继承
    uint32_t seq_num;
    uint8_t  payload[64];
};

// 子类:确认包
struct AckPacket {
    struct PacketHeader hdr;   // ★ 同样继承
    uint32_t ack_num;
    uint32_t window;
};

3.3 内存布局详解(理解了这个就理解了继承)

struct PacketHeader { uint32_t src; uint32_t dst; uint16_t len; };
// 偏移:  0            4            8           10

struct DataPacket {
    struct PacketHeader hdr;   // 偏移 0~11
    uint32_t seq_num;          // 偏移 12
    uint8_t  payload[64];      // 偏移 16
};

// 关键:
struct DataPacket dp;
struct PacketHeader *hp = &dp.hdr;   // hp == &dp,地址完全相同!
DataPacket 内存:
地址: 0x00  0x04  0x08  0x0C  0x10  0x50
      ┌─────┬─────┬─────┬─────┬──────────┐
      │ src │ dst │ len │ seq │ payload  │
      └─────┴─────┴─────┴─────┴──────────┘
      ↑ hdr 部分 (PacketHeader)       ↑ 子类特有
      &dp.hdr == &dp  ← 地址相同!

因为地址相同,任何需要 PacketHeader* 的地方都可以传入 &dp.hdr,这就是向上转型(类似 C++ 的 Derived*Base*)。

3.4 完整模板:Base + Derived

// ==================== Base.h ====================
#ifndef BASE_H
#define BASE_H

typedef struct {
    int id;
    char name[32];
} Base;

void Base_init(Base *me, int id, const char *name);
void Base_print(const Base *me);

#endif
// ==================== Base.c ====================
#include "Base.h"
#include <stdio.h>
#include <string.h>

void Base_init(Base *me, int id, const char *name) {
    me->id = id;
    strncpy(me->name, name, sizeof(me->name) - 1);
    me->name[sizeof(me->name) - 1] = '\0';
}
void Base_print(const Base *me) {
    printf("[%d] %s", me->id, me->name);
}
// ==================== Derived.h ====================
#ifndef DERIVED_H
#define DERIVED_H

#include "Base.h"

typedef struct {
    Base   base;           // ★ 继承:必须是第一个成员
    int    extra;
} Derived;

void Derived_init(Derived *me, int id, const char *name, int extra);
void Derived_print(Derived *me);   // "覆盖"基类方法

#endif
// ==================== Derived.c ====================
#include "Derived.h"
#include <stdio.h>

void Derived_init(Derived *me, int id, const char *name, int extra) {
    Base_init(&me->base, id, name);   // 先初始化基类
    me->extra = extra;                  // 再初始化自己的
}

/* 覆盖:定义同名函数,内部调用基类方法 */
void Derived_print(Derived *me) {
    Base_print(&me->base);              // 类似 C++ 的 Base::print()
    printf(", extra=%d\n", me->extra);
}
// ==================== main.c ====================
#include "Derived.h"
#include <stdio.h>

int main(void) {
    Derived d;
    Derived_init(&d, 1, "Alice", 999);
    Derived_print(&d);            // → [1] Alice, extra=999

    /* 向上转型:Derived* → Base*(安全,因为地址相同) */
    Base *bp = &d.base;
    Base_print(bp);               // → [1] Alice
    putchar('\n');
    return 0;
}
gcc -std=c11 -Wall -Wextra -Werror -pedantic Base.c Derived.c main.c -o inherit_test
./inherit_test

预期输出:

[1] Alice, extra=999
[1] Alice

3.5 对照 C++

// C++                               // C
class Base {                         // Base.h
public:
    Base(int id, const char *n);     // Base_init(Base*, int, const char*)
    void print();                    // Base_print(const Base*)
private:
    int id; char name[32];
};

class Derived : public Base {        // Derived.h: Base base 作为第一成员
public:
    Derived(int id, const char *n, int e)
        : Base(id, n), extra(e) {}   // Derived_init → Base_init(&me->base,...)
    void print() {                   // Derived_print
        Base::print();               // Base_print(&me->base)
        cout << extra;
    }
private:
    int extra;
};

// 向上转型
Base *bp = &d;                       // Base *bp = &d.base;

四、多态:同一个接口,不同的行为

4.1 你遇到的问题

void draw_shape(int type, void *shape) {
    if (type == 0) {      // Circle
        Circle *c = (Circle *)shape;
        draw_circle(c);
    } else if (type == 1) { // Rect
        Rect *r = (Rect *)shape;
        draw_rect(r);
    }
    // 每加一种新形状,就要加一个 else if!
}

4.2 解决方案:ops 表(虚函数表)

核心思想:

  1. 定义一个函数指针结构体(= C++ 的虚函数表 vtable)
  2. 基类里放一个指向这个结构体的指针(= vptr)
  3. 每个子类提供自己的 ops 表实例
  4. 调用时通过 obj->ops->method(obj) 动态分发

4.3 完整模板:带虚函数的基类体系

// ==================== Base.h(带 vtable 的基类模板) ====================
#ifndef BASE_H
#define BASE_H

/* ---- 虚函数表定义(相当于 C++ 的 vtable) ---- */
typedef struct BaseOps {
    void   (*method1)(void *me, int arg);
    int    (*method2)(void *me);
    void   (*destroy)(void *me);      // 可选:析构
} BaseOps;

/* ---- 基类 ---- */
typedef struct {
    const BaseOps *ops;      // ★ vptr:指向子类自己的 ops 表
    int   id;
} Base;

/* ---- 统一调度接口(这是你对外调用的入口) ---- */
void   Base_method1(Base *me, int arg);
int    Base_method2(Base *me);
void   Base_destroy(Base *me);

#endif
// ==================== Base.c(调度实现) ====================
#include "Base.h"
#include <assert.h>

void Base_method1(Base *me, int arg) {
    assert(me->ops && me->ops->method1);   // 类似 C++ 纯虚函数检查
    me->ops->method1(me, arg);             // ★ 动态分发!
}

int Base_method2(Base *me) {
    assert(me->ops && me->ops->method2);
    return me->ops->method2(me);
}

void Base_destroy(Base *me) {
    if (me->ops && me->ops->destroy)
        me->ops->destroy(me);
}
// ==================== Derived.h(子类模板) ====================
#ifndef DERIVED_H
#define DERIVED_H

#include "Base.h"

typedef struct {
    Base   base;             // ★ 继承基类(含 vptr)
    int    private_data;
} Derived;

void Derived_init(Derived *me, int id, int data);

#endif
// ==================== Derived.c(子类实现) ====================
#include "Derived.h"
#include <stdio.h>

/* ---- private 方法:子类自己的实现 ---- */
static void Derived_method1(void *me, int arg) {
    Derived *self = (Derived *)me;
    printf("Derived.method1(%d), data=%d\n", arg, self->private_data);
}
static int Derived_method2(void *me) {
    Derived *self = (Derived *)me;
    return self->private_data * 2;
}

/* ---- 子类的 ops 表(相当于 C++ 的子类 vtable) ---- */
static const BaseOps DERIVED_OPS = {
    .method1 = Derived_method1,
    .method2 = Derived_method2,
    .destroy = NULL,
};

/* ---- 构造:关键 = 绑定 ops ---- */
void Derived_init(Derived *me, int id, int data) {
    me->base.ops = &DERIVED_OPS;   // ★ 让 vptr 指向子类的 ops
    me->base.id  = id;
    me->private_data = data;
}
// ==================== main.c(多态使用) ====================
#include "Derived.h"
#include <stdio.h>

/* 这个函数完全不知道传进来的是哪个子类 */
void client_code(Base *b) {
    Base_method1(b, 42);          // → 自动派发到子类实现
    int r = Base_method2(b);
    printf("result=%d\n", r);
}

int main(void) {
    Derived d;
    Derived_init(&d, 1, 100);

    client_code((Base *)&d);      // 向上转型 → 多态!
    return 0;
}
gcc -std=c11 -Wall -Wextra -Werror -pedantic Base.c Derived.c main.c -o polymorphism_test
./polymorphism_test

预期输出:

Derived.method1(42), data=100
result=200

4.4 实际例子:Stream 抽象

不同"流"有相同的读写接口,但底层实现完全不同:

// ==================== stream.h ====================
#ifndef STREAM_H
#define STREAM_H

#include <stddef.h>

typedef struct Stream Stream;
typedef struct StreamOps {
    int    (*open)(Stream *me, const char *path);
    void   (*close)(Stream *me);
    size_t (*read)(Stream *me, char *buf, size_t size);
    size_t (*write)(Stream *me, const char *buf, size_t size);
} StreamOps;

struct Stream {
    const StreamOps *ops;
};

int  Stream_open(Stream *me, const char *path);
void Stream_close(Stream *me);
size_t Stream_read(Stream *me, char *buf, size_t size);
size_t Stream_write(Stream *me, const char *buf, size_t size);

#endif
// ==================== stream.c ====================
#include "stream.h"
#include <assert.h>

int Stream_open(Stream *me, const char *path) {
    assert(me != NULL && me->ops != NULL && me->ops->open != NULL);
    return me->ops->open(me, path);
}
void Stream_close(Stream *me) {
    assert(me != NULL && me->ops != NULL && me->ops->close != NULL);
    me->ops->close(me);
}
size_t Stream_read(Stream *me, char *buf, size_t size) {
    assert(me != NULL && me->ops != NULL && me->ops->read != NULL);
    return me->ops->read(me, buf, size);
}
size_t Stream_write(Stream *me, const char *buf, size_t size) {
    assert(me != NULL && me->ops != NULL && me->ops->write != NULL);
    return me->ops->write(me, buf, size);
}
// ==================== memory_stream.h ====================
#ifndef MEMORY_STREAM_H
#define MEMORY_STREAM_H

#include "stream.h"

typedef struct {
    Stream base;
    const char *data;
    size_t pos;
} MemoryStream;

void MemoryStream_init(MemoryStream *me, const char *data);

#endif
// ==================== memory_stream.c ====================
#include "memory_stream.h"
#include <string.h>

static int memory_open(Stream *me, const char *path) {
    (void)me;
    (void)path;
    return 0;
}
static void memory_close(Stream *me) {
    (void)me;
}
static size_t memory_read(Stream *me, char *buf, size_t size) {
    MemoryStream *self = (MemoryStream *)me;
    size_t remaining = strlen(self->data) - self->pos;
    size_t n = remaining < size ? remaining : size;
    memcpy(buf, self->data + self->pos, n);
    self->pos += n;
    return n;
}
static size_t memory_write(Stream *me, const char *buf, size_t size) {
    (void)me;
    (void)buf;
    (void)size;
    return 0;
}

static const StreamOps MEMORY_STREAM_OPS = {
    memory_open, memory_close, memory_read, memory_write
};

void MemoryStream_init(MemoryStream *me, const char *data) {
    me->base.ops = &MEMORY_STREAM_OPS;
    me->data = data;
    me->pos = 0;
}
// ==================== null_stream.h ====================
#ifndef NULL_STREAM_H
#define NULL_STREAM_H

#include "stream.h"

typedef struct {
    Stream base;
    size_t bytes_written;
} NullStream;

void NullStream_init(NullStream *me);

#endif
// ==================== null_stream.c ====================
#include "null_stream.h"

static int ns_open(Stream *me, const char *path) {
    (void)me;
    (void)path;
    return 0;
}
static void ns_close(Stream *me) {
    (void)me;
}
static size_t ns_read(Stream *me, char *buf, size_t size) {
    (void)me;
    (void)buf;
    (void)size;
    return 0;
}
static size_t ns_write(Stream *me, const char *buf, size_t size) {
    NullStream *self = (NullStream *)me;
    (void)buf;
    self->bytes_written += size;
    return size;
}

static const StreamOps NULL_STREAM_OPS = {
    ns_open, ns_close, ns_read, ns_write
};

void NullStream_init(NullStream *me) {
    me->base.ops = &NULL_STREAM_OPS;
    me->bytes_written = 0;
}
// ==================== main.c ====================
#include "stream.h"
#include "memory_stream.h"
#include "null_stream.h"
#include <assert.h>
#include <stdio.h>

void copy_data(Stream *in, Stream *out) {
    char buf[4];
    size_t n;
    while ((n = Stream_read(in, buf, sizeof(buf))) > 0) {
        assert(Stream_write(out, buf, n) == n);
    }
}

int main(void) {
    MemoryStream in;
    NullStream out;

    MemoryStream_init(&in, "hello");
    NullStream_init(&out);

    assert(Stream_open((Stream *)&in, NULL) == 0);
    assert(Stream_open((Stream *)&out, NULL) == 0);
    copy_data((Stream *)&in, (Stream *)&out);

    printf("bytes_written=%zu\n", out.bytes_written);
    Stream_close((Stream *)&in);
    Stream_close((Stream *)&out);
    return 0;
}
gcc -std=c11 -Wall -Wextra -Werror -pedantic stream.c memory_stream.c null_stream.c main.c -o stream_test
./stream_test

预期输出:bytes_written=5

4.5 多态的底层本质

C++ 编译后的内存布局:            C 手动布局:
┌───────────────────────┐       ┌───────────────────────┐
│ vptr  ─────→ vtable   │       │ ops  ─────→ ops 表    │
│ fd                    │       │ fd                    │
│ FILE* (FileStream)    │       │ FILE* (FileStream)    │
└───────────────────────┘       └───────────────────────┘

C++:   stream->read(buf, n)          → stream->vptr->read(stream, buf, n)
C:     Stream_read(&fs, buf, n)      → fs.base.ops->read(&fs, buf, n)

完全一致。C++ 编译器帮你写的,C 里你自己写。

五、向下转型:container_of 原理

5.1 问题

你已经通过 Base *bp = &derived.base; 向上转型了。现在你想拿回 Derived *

5.2 核心宏

#include <stddef.h>  // 或者自己写

// 计算成员在结构体中的字节偏移
#define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)

// 从成员指针反推结构体指针
#define container_of(ptr, type, member) ({               \
    void *__mptr = (void *)(ptr);                        \
    ((type *)(__mptr - offsetof(type, member)));         \
})

原理:已知结构体某个成员的地址,减去该成员在结构体中的偏移量,就得到结构体的起始地址。

成员地址  →  __mptr
减去      →  offsetof(type, member)
得到      →  结构体起始地址

5.3 典型用法

// 场景 1:多态回调中需要子类特有字段
static int fs_read(void *me, char *buf, int size) {
    // me 是 Stream*,但我们需要 FileStream 的 FILE*
    FileStream *self = container_of((Stream *)me, FileStream, base);
    return fread(buf, 1, size, self->fp);
}

// 场景 2:Linux 内核 workqueue
struct my_device {
    int   irq_num;
    struct work_struct work;      // 内核结构体内嵌
};
static void my_work_handler(struct work_struct *work) {
    // 内核只给了 work_struct*,要拿回 my_device*
    struct my_device *dev = container_of(work, struct my_device, work);
    // 现在可以访问 dev->irq_num
}

六、四层架构:工业级项目怎么组织

当你有了多个类(封装)、类之间有继承、有些类需要多态,怎么组织代码?

6.1 标准四层

┌─────────────────────────────────────────────┐
│  app.c          应用层                       │
│  只操作基类指针,不出现任何硬件关键字           │
├─────────────────────────────────────────────┤
│  board_init.c   板级绑定层                    │
│  实例化具体对象,绑定 ops,暴露全局指针         │
├─────────────────────────────────────────────┤
│  xxx_subclass.c 子类实现层                    │
│  每个子类一个文件:struct + ops 表 + init     │
├─────────────────────────────────────────────┤
│  base.h/c       基类定义层                    │
│  接口 + ops 表类型 + 统一调度函数              │
└─────────────────────────────────────────────┘

6.2 完整四层模板

第 1 层 — 基类(device.h/.c):

// ==================== device.h ====================
#ifndef DEVICE_H
#define DEVICE_H

#include <stddef.h>

typedef struct DeviceOps {
    int    (*init)(void *me);
    size_t (*read)(void *me, char *buf, size_t len);
    size_t (*write)(void *me, const char *buf, size_t len);
    void   (*deinit)(void *me);
} DeviceOps;

typedef struct {
    const DeviceOps *ops;
    char  name[16];
    int   state;   // 0=closed, 1=ready
} Device;

int  Device_init(Device *me);
size_t Device_read(Device *me, char *buf, size_t len);
size_t Device_write(Device *me, const char *buf, size_t len);
void Device_deinit(Device *me);

#endif
// ==================== device.c ====================
#include "device.h"
#include <assert.h>

int Device_init(Device *me) {
    assert(me != 0 && me->ops != 0 && me->ops->init != 0);
    return me->ops->init(me);
}
size_t Device_read(Device *me, char *buf, size_t len) {
    assert(me != 0 && me->ops != 0 && me->ops->read != 0);
    return me->ops->read(me, buf, len);
}
size_t Device_write(Device *me, const char *buf, size_t len) {
    assert(me != 0 && me->ops != 0 && me->ops->write != 0);
    return me->ops->write(me, buf, len);
}
void Device_deinit(Device *me) {
    if (me != 0 && me->ops != 0 && me->ops->deinit != 0)
        me->ops->deinit(me);
}

第 2 层 — 子类实现(uart_device.c):

// ==================== uart_device.h ====================
#ifndef UART_DEVICE_H
#define UART_DEVICE_H

#include "device.h"

typedef struct {
    Device base;
    int    uart_num;
    int    baud;
    const char *rx_data;
    size_t rx_pos;
} UartDevice;

void UartDevice_init(UartDevice *me, const char *name, int uart_num, int baud, const char *rx_data);

#endif
// ==================== uart_device.c ====================
#include "uart_device.h"
#include <stdio.h>
#include <string.h>

static int uart_init(void *me) {
    UartDevice *self = (UartDevice *)me;
    self->base.state = 1;
    printf("%s: UART%d init %d baud\n", self->base.name, self->uart_num, self->baud);
    return 0;
}
static size_t uart_read(void *me, char *buf, size_t len) {
    UartDevice *self = (UartDevice *)me;
    size_t left = strlen(self->rx_data) - self->rx_pos;
    size_t n = left < len ? left : len;
    memcpy(buf, self->rx_data + self->rx_pos, n);
    self->rx_pos += n;
    return n;
}
static size_t uart_write(void *me, const char *buf, size_t len) {
    UartDevice *self = (UartDevice *)me;
    printf("%s write: %.*s", self->base.name, (int)len, buf);
    return len;
}
static void uart_deinit(void *me) {
    UartDevice *self = (UartDevice *)me;
    self->base.state = 0;
    printf("%s: deinit\n", self->base.name);
}

static const DeviceOps UART_OPS = {
    .init   = uart_init,
    .read   = uart_read,
    .write  = uart_write,
    .deinit = uart_deinit,
};

void UartDevice_init(UartDevice *me, const char *name, int uart_num, int baud, const char *rx_data) {
    me->base.ops = &UART_OPS;
    snprintf(me->base.name, sizeof(me->base.name), "%s", name);
    me->base.state = 0;
    me->uart_num = uart_num;
    me->baud = baud;
    me->rx_data = rx_data;
    me->rx_pos = 0;
}

第 3 层 — 板级绑定(board_init.c):

// ==================== board.h ====================
#ifndef BOARD_H
#define BOARD_H

#include "device.h"

extern Device *g_console;
extern Device *g_gps;

void board_init(void);
void board_deinit(void);

#endif
// ==================== board.c ====================
#include "uart_device.h"
#include "board.h"

static UartDevice console;
static UartDevice gps;

Device *g_console;      // 全局指针——暴露给应用层
Device *g_gps;

void board_init(void) {
    UartDevice_init(&console, "console", 1, 115200, "");
    g_console = (Device *)&console;
    Device_init(g_console);   // 调用 init ops

    UartDevice_init(&gps, "gps", 2, 9600, "GPS:OK\n");
    g_gps = (Device *)&gps;
    Device_init(g_gps);
}

void board_deinit(void) {
    Device_deinit(g_console);
    Device_deinit(g_gps);
}

第 4 层 — 应用层(app.c):

// ==================== app.c ====================
#include "board.h"
#include <stdio.h>
#include <string.h>

// 完全不依赖 UART/GPIO/SPI 等任何硬件关键字
void app_main(void) {
    char buf[64];
    size_t n;

    Device_write(g_console, "Hello\n", strlen("Hello\n"));
    n = Device_read(g_gps, buf, sizeof(buf) - 1);
    buf[n] = '\0';
    printf("gps read: %s", buf);
}
// ==================== main.c ====================
#include "board.h"

void app_main(void);

int main(void) {
    board_init();
    app_main();
    board_deinit();
    return 0;
}
gcc -std=c11 -Wall -Wextra -Werror -pedantic device.c uart_device.c board.c app.c main.c -o device_test
./device_test

预期输出:

console: UART1 init 115200 baud
gps: UART2 init 9600 baud
console write: Hello
gps read: GPS:OK
console: deinit
gps: deinit

6.3 换芯片时的改动

旧芯片                 新芯片
board_init.c:          board_init.c:
  UartDevice_init(...)    → 改为新芯片的 UART 驱动
  g_console = ...         → 指针类型不变,名字不变

app.c: 零改动!          app.c: 零改动!

七、Linux 内核中的 C-OOP

这不是理论,这是 Linux 内核 4000 万行 C 代码每天都在用的模式。

7.1 file_operations — 最经典的 ops 表

// Linux/include/linux/fs.h
struct file_operations {
    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 *);
    // ... 几十个函数指针
};

// 每个驱动提供一个自己的 file_operations 实例
const struct file_operations ext4_file_operations = {
    .read   = ext4_file_read,
    .write  = ext4_file_write,
    .open   = ext4_file_open,
    // ...
};
const struct file_operations socket_file_ops = {
    .read   = sock_read,
    .write  = sock_write,
    // ...
};

// VFS 统一调用,自动分发
ssize_t vfs_read(struct file *file, ...) {
    return file->f_op->read(file, buf, size, pos);  // 多态!
}

7.2 container_of 在内核中的使用

// Linux 设备驱动标准模式
struct my_device {
    int           irq;
    struct device dev;           // 内核设备模型结构体
    struct work_struct work;     // 工作队列
};

// 内核只回调 work 函数,用 container_of 拿回 my_device
static void my_work_handler(struct work_struct *work) {
    struct my_device *mdev = container_of(work, struct my_device, work);
    complete(&mdev->done);
}

// probe 时注册
static int my_probe(struct platform_device *pdev) {
    struct my_device *mdev;
    mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
    INIT_WORK(&mdev->work, my_work_handler);
    platform_set_drvdata(pdev, mdev);
}

7.3 list_head — 侵入式链表

// Linux/include/linux/types.h
struct list_head {
    struct list_head *next, *prev;
};

// 核心遍历宏
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)

// 从节点反查结构体
#define list_entry(ptr, type, member)  container_of(ptr, type, member)

// 一步到位遍历业务对象
#define list_for_each_entry(pos, head, member)                  \
    for (pos = list_entry((head)->next, typeof(*pos), member);  \
         &pos->member != (head);                                \
         pos = list_entry(pos->member.next, typeof(*pos), member))
// 使用例子
struct my_data {
    int id;
    struct list_head node;   // 侵入式节点
};

LIST_HEAD(data_list);       // 初始化链表头

// 添加
struct my_data *d = malloc(sizeof(*d));
d->id = 42;
list_add_tail(&d->node, &data_list);

// 遍历(无需递归、无需索引)
struct my_data *pos;
list_for_each_entry(pos, &data_list, node) {
    printf("id=%d\n", pos->id);
}

7.4 内核 C-OOP 对照表

内核概念 你学到的 C-OOP 概念
struct file_operations ops 表(vtable)
struct i2c_algorithm 协议层的 ops 表
struct gpio_chip 硬件抽象层的 ops 表
container_of 向下转型
list_head + list_entry 侵入式容器
module_init 自动注册(__attribute__((section()))
platform_driver.probe 构造函数 + 绑定
devm_kzalloc 构造中分配资源

八、速查卡(打印贴墙用)

8.1 创建一个新类的步骤

1. 复制模板(见 §2.3 XXX.h + XXX.c)
2. 全局替换 XXX → 你的类名
3. 在 struct XXX { ... }; 中加你的字段
4. 实现 init/deinit 和各方法
5. #include 并使用

8.2 C++/Java → C 速查表

C++/Java 概念              →  C 实现
────────────────────────────────────────────────
class Student {             →  Student.h + Student.c
private: int id;            →  struct Student { int id; }; (在 .c 里)
public: void setId(int);    →  Student.h 中:void Student_setId(Student*, int);
this->id = id;              →  me->id = id;
Student(int id, int g)      →  void Student_init(Student*, int, int);
~Student()                  →  void Student_deinit(Student*);
继承:class B : public A    →  struct B { A base; int extra; };(A 是第一成员)
super.method();             →  A_method(&me->base);
virtual void foo() = 0;     →  ops 表 + assert(ops->foo != NULL)
多态调用:a->foo()           →  me->ops->foo(me);
namespace XXX               →  函数前缀 XXX_(例:XXX_init)
模板:List<T>               →  宏 + void*
异常 try/catch              →  返回错误码 + assert

8.3 关键字用法

关键字 在 C-OOP 中的角色
struct 定义对象的属性集合(= C++ class 的成员变量)
static(文件域) private 函数/变量(仅本 .c 可见)
static(函数内) 跨调用保持状态的局部变量
const 只读常量/参数表
typedef 隐藏struct 关键字,简化类型名
extern 声明其他文件定义的全局变量
void * ops 回调中的泛型指针("不知道具体类型,先拿着")
__attribute__ GCC 扩展:段控制、对齐、弱符号等

8.4 常用宏

/* 成员偏移量 */
#define offsetof(TYPE, MEMBER)    ((size_t)&((TYPE *)0)->MEMBER)

/* 成员指针 → 结构体指针 */
#define container_of(ptr, type, member) ({            \
    void *__mptr = (void *)(ptr);                     \
    ((type *)(__mptr - offsetof(type, member)));      \
})

/* 遍历链表 */
#define list_for_each(pos, head) \
    for (pos = (head)->next; pos != (head); pos = pos->next)
#define list_entry(ptr, type, member)  container_of(ptr, type, member)
#define list_for_each_entry(pos, head, member)              \
    for (pos = list_entry((head)->next, typeof(*pos), member); \
         &pos->member != (head);                            \
         pos = list_entry(pos->member.next, typeof(*pos), member))

/* 数组长度 */
#define ARRAY_SIZE(arr)  (sizeof(arr) / sizeof((arr)[0]))

/* 安全 max/min */
#define MAX(a,b)  ({ __typeof__(a) _a = (a); __typeof__(b) _b = (b); _a > _b ? _a : _b; })
#define MIN(a,b)  ({ __typeof__(a) _a = (a); __typeof__(b) _b = (b); _a < _b ? _a : _b; })

/* 位操作 */
#define BIT(n)          (1UL << (n))
#define SET_BIT(reg, n) ((reg) |= BIT(n))
#define CLR_BIT(reg, n) ((reg) &= ~BIT(n))

/* 自动注册(裸机版) */
#define __init_call __attribute__((section(".initcall")))
#define MODULE_INIT(fn)  static void (*__init_##fn)(void) __init_call = fn

核验资料:

来源:B 站课程《C语言OOP封装完整系列》(500强嵌入式工程师) + Linux 内核源码 关联笔记:[[C++类与对象]] 对比 C++ class 底层原理