函数指针 + 结构体 = C 语言的“对象模型”

补充知识点:

从 C 对象模型看 JNI:一行 (*env)->CallVoidMethod 背后的系统级真相

从函数表到 JNIEnv:彻底看懂 JNI 中的二级指针、结构体函数表与 -> 语法糖

一、为什么 C 语言需要"对象模型"?

在 C 语言里,只有两种基本东西:

  • ✅ 数据(变量 / struct)

  • ✅ 函数(全局函数)

它没有:

  • class
  • method
  • interface
  • virtual
  • 多态

但系统软件(操作系统、虚拟机、驱动、中间件)必须要有:

  • 抽象接口
  • 模块解耦
  • 运行时替换实现
  • 多态调用

于是,C 语言世界里诞生了一种"约定俗成"的结构:

👉 struct(保存状态) + 函数指针(保存行为)

这套组合,就是 C 语言的"对象模型"。

二、最小对象模型:状态 + 行为

先从一个极小例子开始,看 C 如何"造对象"。

🎯 目标:做一个"计数器对象"

我们希望它有:

  • 状态:value

  • 方法:inc() / get()

✅ C 语言实现:

cpp 复制代码
#include <stdio.h>

typedef struct Counter Counter;

struct Counter {
    int value;                              // 状态
    void (*inc)(Counter* self);             // 行为
    int  (*get)(Counter* self);
};

void counter_inc(Counter* self) {
    self->value++;
}

int counter_get(Counter* self) {
    return self->value;
}

int main() {
    Counter c;
    c.value = 0;
    c.inc = counter_inc;
    c.get = counter_get;

    c.inc(&c);
    c.inc(&c);
    printf("%d\n", c.get(&c)); // 2
}

🔍 这里发生了什么?

  • struct Counter 保存状态
  • 函数指针保存"方法"
  • self 参数 = C 里的 this
  • c.inc(&c) = 对象调用方法

👉 这已经是一个完整意义上的"对象"。

三、为什么函数指针一定要带 self?

因为 C 的函数不属于任何对象。

void counter_inc(Counter* self)

是普通函数,不知道"我是谁"。

所以:

👉 必须手动把对象传进去。

这在系统层极其常见:

cpp 复制代码
int (*read)(struct Device* dev, void* buf, int len);
int (*start)(struct Engine* engine);

第一个参数,几乎永远是:

  • self
  • ctx
  • handle
  • object

👉 它就是"对象上下文"。

四、对象能力从哪来?三大核心特性

✅ 1. 封装(Encapsulation)

调用方不直接操作内部数据,只通过函数指针访问:

c.inc(&c);

✅ 2. 多态(Polymorphism)

同一个结构体接口,可以绑定不同实现:

cpp 复制代码
void inc1(Counter* c) { c->value += 1; }
void inc2(Counter* c) { c->value += 2; }

c.inc = inc1; // 普通计数器
c.inc = inc2; // 加速计数器

👉 调用代码不变,行为变化。

✅ 3. 解耦(Decoupling)

调用方不关心:

  • 函数叫什么
  • 函数在哪
  • 如何实现

它只认:

👉 这个 struct 里有哪些"能力"。

五、进阶:vtable(虚函数表)模型

系统层通常不会把函数指针直接塞进对象,

而是拆成两层:

  • 对象:状态

  • vtable:方法表(共享)

✅ 结构升级版:

cpp 复制代码
typedef struct Counter Counter;
typedef struct CounterVTable CounterVTable;

struct CounterVTable {
    void (*inc)(Counter* self);
    int  (*get)(Counter* self);
};

struct Counter {
    int value;
    const CounterVTable* vptr;
};

✅ 实现:

cpp 复制代码
void counter_inc(Counter* self) { self->value++; }
int  counter_get(Counter* self) { return self->value; }

const CounterVTable COUNTER_VT = {
    .inc = counter_inc,
    .get = counter_get
};

✅ 使用:

cpp 复制代码
Counter c = { .value = 0, .vptr = &COUNTER_VT };
c.vptr->inc(&c);
printf("%d\n", c.vptr->get(&c));

👉 这已经和 C++ 虚函数机制几乎完全一致:

  • 对象里有 vptr
  • vptr 指向函数表
  • 调用 = 查表 + 跳转

六、这套模型正是系统世界的通用设计

✅ Linux 内核

cpp 复制代码
struct file_operations {
    int (*open)(struct inode*, struct file*);
    ssize_t (*read)(struct file*, char*, size_t, loff_t*);
    ssize_t (*write)(...);
};

不同文件系统 → 填不同 ops 表。

✅ HAL / 驱动 / 中间件

cpp 复制代码
struct audio_hw_device {
    int (*init)(...);
    int (*start)(...);
    int (*stop)(...);
};

✅ JNI(你非常熟)

cpp 复制代码
struct JNINativeInterface_ {
    jclass (*FindClass)(JNIEnv*, const char*);
    jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
    ...
};

(*env)->FindClass(env, "java/lang/String");

👉 典型:vtable + self + 查表调用。

七、统一抽象视角(非常重要)

以后你看到任何系统接口,只要问三件事:

  1. 这个 struct 是不是"对象"?
  2. 这些函数指针是不是"方法表"?
  3. 第一个参数是不是 self / ctx?

如果是 →

👉 这就是 C 语言对象模型。

八、和 C++ / Java 的一一对应

C 系统层 C++ / Java
struct class
函数指针 method
self 参数 this
vtable 虚函数表
ops 结构体 interface
运行时赋值 多态

👉 所谓"面向对象",在底层几乎都落回这一套。

九、一句话系统级总结

C 没有类,

但用 struct 保存状态,

用函数指针保存行为,

用 vtable 共享方法,

就造出了整个系统世界。

Linux、Android、JVM、驱动、NDK,没有例外。

下一篇:

从 C 对象模型 → JNI → HAL → Linux 内核接口------一条贯穿系统软件的完整认知链

相关推荐
Liuqy-052 天前
TCP与UDP
网络·网络协议·tcp/ip·udp·c
pipixia200339 天前
C++继承面试四连问:内存叠加、私有继承、sizeof、权限重映射
c++·面试·嵌入式·继承·面向对象·对象模型
C++ 老炮儿的技术栈9 天前
机器人的坐标,像 XYZ 的坐标值,是用单精度浮点还是用双精度浮点?
c语言·c++·人工智能·单片机·c
wflynn10 天前
GitHub 今日推荐|gpu-unlock-ada:Ada 显卡张量算力翻倍解锁
开源·github·c
wflynn11 天前
GitHub 今日推荐|dquickts:能暂停、能存档的 QuickJS
开源·github·c·durable-execution·embeddable·hibernation
real_metrix13 天前
【学习笔记】C/C++ 头文件自包含
c++·c·头文件·自包含
闻缺陷则喜何志丹14 天前
计算24点
c·测试·input·output
2601_9555181817 天前
C# 数组 - FindIndex() 方法
c·教程·数组·示例·findindex()
从小白到软工18 天前
3-8 用draw.io来绘制界面*
c
从小白到软工18 天前
3-7 项目-图形版井字棋(下)
c