如何为C语言添加一个对象系统
为C语言添加OO能力的尝试从上世纪70年代到现在一直没有停止过,除了大获成的C++/Objective-C以外,还有很多其它的成功案例,比如GTK在libg中实现了一个对象系统,还有前几年一个OOC,以及很多用宏实现的所谓轻量级OO系统。上周在网上发现了又一个自称为OOC系统,我决定总结一下这方面的内容。
大部分面向对象系统可以分成两类,一类是基于原型的设计,类似javascript;另一类是基于类模板的设计,比如C++/Java。当然,这不是绝对化,近几年,在很多动态语言实现中,有很多混搭的实现,例如Dart。因为有C++的例子,基于类模板的对象系统可能对C语言程序员更自然一些,我们以此为例。这个系统要改成一个基于原型的系统也非常简单。
对象系统中最核心的概念当然是对象。对象在C语言中没有直接的对应成份(没有内置于语言),我们可以选择这么几种来表示对象,一是无类型的指针,二是结构体指针,三是表示为int的ID。从本质上来看,这些并没有区别,无非是语法上简洁和复杂。我们选择一个结构指针类型struct object*来表示对象.
对象之间的消息传递,对于C/C++等命令式语言(相对于函数式语言)来说,都对应于一个函数调用。像C++语言一样,我们可以定义一个虚表;也可以像Objective-C一样定义一个消息转发链。从实际效果上来说,都有以下过程:
struct object* a;
member_function* pf = find_function(a, the-function-id);
pf(a, other-param);
以上伪代码合并成一行调用,我们定义一个向对象a发送为function_id的消息,使用如下语法:
interface(a)->function_id(a, other-param);
这里引入了一个概念interface(接口),接口是一组消息的集合,对象可以接受的消息由它实现的接口定义,发送消息即变成取得相应接口,并调用接口上的函数。这个设计综合了虛表和消息转发链的设计,比较接近于COM中的接口概念。接口以类似虚表的形式定义:
struct XXX_interface {
struct object* kclass;
int (*XXX_function)(struct object* this_object, other-param);
…
};
接口实际上暗示了我们的实现是对象->接口表->接口->类(运行时信息)。借用下图,左侧蓝色为对象,这两个对象是属于同一个类,它有一个成员_vtab指向右侧黄色的一个虛表或者说是接口表,接口表有一个成员_class指向相应的类数据。接口表和类数据注册到类型系统中,而对象由用户分配内存。

有了接口概念我们可以实现接口继承,但实现继承需要另一个机制,我们不打算像C++选择多重继承,而是直接选择更为直接的Mixup(混入)方式。我们可以通过在类型构造时直接调用mixup,传入类型对象和mixup结构。
这里我们会遇到为C语言添加OO支持最大的困难,我们没有办法在编译期添加特性,比如构造类/生成指针表/混入实现,我们只能选择在运行时添加一个class_init,类型初始化函数。这个问题带来了两个不足之处,一是有很多记簿的工作需要程序员完成;二是无法实现静态对象。每个类型需要这样一个初始化过程:
struct klass XXXClass = {
};
void XXX_init() {
declare(&XXXClasss, &baseClass);
XXXClasss.init = XXX_init;
…
struct XXX_interface i* = implement(&XXXClasss, &XXX_interface)
i->function_id = some_implement_function;
…
mixup(&XXXClass, &XXX_mixup);
…
register(&XXXClass);
}
这里用到一个struct klass结构,它的作用是记录对象的相关信息,主要内容如下:
struct klass {
int id;
char* name;
size_t object_size;
struct klass* parent;
size_t itable_size;
struct itable* itables;
void (* init) ( Class this ); /* class initializer */
void (* ctor) (Object self, const void * params ); /* constructor */
void (* dtor) (Object self, Vtable vtab); /* destructor */
int (* copy) (Object self, const Object from); /* copy constructor */
};
当Declare这个对象时,系统开始记录它的id/name并计算object_size。后面一系列代码用于初始化基本的函数指针和接口表指针。最后Register这个类到系统中,用于动态类型查找。每个类这些记簿式的代码非常类似。
前面用到的interface(a)这个函数就是通过遍历itables来找到对应的接口虛表,接口表有反向指针指回类说明,因此可以通过一个接口来查询其它接口。
在main函数的开始部分,需要对整个对象系统手动初始化,这可以说是一段非常不人道的代码:
int main() {
object_system_init();
XXX_init();
XXX2_init();
…
}
虽然我们可以声明一个数组来完成对各个init函数的自动调用,但这个声明过程依然非常不人道。要得到对程序员比较友好的过程,我们需要通过一个额外的源代码分析过程,自动生成上面class_init函数,以及system_init过程。
分配一个对象,事实上只需要三步,一是找到对应的类型,二是分配空间,三是设置虚表指针。第一步,可以直接使用全局的静态struct kclass对象,也可通过查找函数find_class("class name")来完成。第二步这步分配空间,可以由用户完成,只需要下一步调用object_new_at(user_space, class)。如果使用系统分配空间即可由object_new一次完成二、三两步。
//用户分配
struct klass XXXClasss;
void* ptr = malloc(XXXClass.object_size);
object_new_at(ptr, &XXXClass);
//系统分配
struct object* ptr = object_new(&XXXClass);
在对象初始化过程中,object_new会调用构造函数,也就是kclass中的init函数,相应的destructor/copy等函数也会在对应的object_destroy/object_copy过程中调用。
以上基本构造了一个简单的对象系统核心,我们如果再补充一些错误处理、内存管理以及多线程处理,一个小型而完整的对象系统就构造出来了,但它最大问题还是语法复杂度比较高。虽然我们可以使用宏来优化语法,但效果不如人意,同时还带来了理解上的困难。
在一些简单的应用中,并不需要这样一个复杂而完整的对象系统,我们更简单的抽象甚至更好一点。一个对象可以表示如下,vtable可以指向一个函数如f(void* data);
struct object {
void* vtable;
void* data;
};
构造和析构函数都专用函数XXX_new和XXX_destroy即可。
如何为C语言添加一个对象系统的更多相关文章
- deb包+软件图标+添加到系统菜单+举例安装卸载
本文介绍的内容和实验一下: 1. 制造deb包.2. 为了使软件图标.3. 开始菜单中添加到系统中的软件:4. 安装和卸载制作的deb包. 1. 制作deb包 制作deb包的方法可能有多种,本文使用的 ...
- 在Jekyll博客添加评论系统:gitment篇
最近在Github Pages上使用Jekyll搭建了个人博客( jacobpan3g.github.io/cn ), 当需要添加评论系统时,找了一下国内的几个第三方评论系统,如"多说&qu ...
- C语言操作WINDOWS系统存储区数字证书相关函数详解及实例
C语言操作WINDOWS系统存储区数字证书相关函数详解及实例 以下代码使用C++实现遍历存储区证书及使用UI选择一个证书 --使用CertOpenSystemStore打开证书存储区. --在循环中 ...
- C语言如何向系统接要存
C语言如何向系统接要存,就有这么三种方式: 1.向栈要. 2.向堆要. 3.向数据段要. 这一下就扯出了三种内存空间,内存空间的本质是一样的,一个地址对应一个方框,方框里可以放数据.但是为了更好的去 ...
- Office 2010 安装程序包的语言不受系统支持
主要看了这篇文章之后让我有了处理思路. 最后我直接用压缩文件进行安装,没有时行解压.这样就不会出现找不到什么文件的问题了.所以语言不受系统支持问题也就解决了. 原文内容: 前几天,有位好友跟我说他的 ...
- 干货 | 携程多语言平台-Shark系统的高可用演进之路
https://mp.weixin.qq.com/s/cycZslUlfyVNm2GVrZm1Cw 干货 | 携程多语言平台-Shark系统的高可用演进之路 原创 Fenlon 携程技术 2020-1 ...
- Autolayout-VFL语言添加约束
一.VFL语言简洁 VFL(Visual format language)语言是苹果为了简化手写Autolayout代码所创建的专门负责编写约束的代码.为我们简化了许多代码量. 二.使用步骤 使用步骤 ...
- Autolayout-VFL语言添加约束-备
一.VFL语言简介 VFL(Visual format language)语言是苹果为了简化手写Autolayout代码所创建的专门负责编写约束的代码.为我们简化了许多代码量. 二.使用步骤 使用步骤 ...
- R语言图形base系统(一)
一般R作图有三大绘图系统:base系统.ggplot2绘图系统.lattice绘图系统. 本篇主要介绍base系统绘图时的图形参数.一般用plot()函数来完成.在R中,若 ...
随机推荐
- ecshop 设置管理员
<?php define('IN_ECS', true); require(dirname(__FILE__) . '/includes/init.php'); $admin_name=trim ...
- ecshop 广告位固定
不知道ECSHOP用户们发现没有,如果在一个广告位中添加多个广告图片, 在前台显示的时候,每刷新一次,图片的显示顺序就会随机变化一次. 注:如果给广告位只添加一个图片是没有这种问题的. 现在的问题是: ...
- 【Python】类和对象、继承、使用文件、存储、异常、标准库(不懂)
当你调用这个对象的方法MyObject.method(arg1, arg2)的时候,这会由Python自动转为MyClass.method(MyObject, arg1, arg2)——这就是self ...
- nginx 负载均衡相关知识
Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器. Nginx 是由 Igor Sysoev ...
- BITMAP CONVERSION FROM ROWIDS
在有些执行计划中,可以会看到 BITMAP CONVERSION FROM ROWIDS这样的东东,也许你会感觉奇怪,我没有使用位图索引怎么出现了bitmap.我通过一个sql和大家分析下原因:sql ...
- vector容器使用总结 .xml
pre{ line-height:1; color:#38ede1; background-color:#5b2814; font-size:16px;}.sysFunc{color:#008080; ...
- linux modprobe.conf怎么不见了—-CentOS 6
用习惯了CentOS或RHEL的用户或许很熟悉/etc/modprobe.conf文件,系统声卡.网卡.SCSI卡的驱动类型都在这里定义,同样部分动态加载的模块的参数也可以在这个文件中定义,但是在Ce ...
- Chapter10:泛型算法
泛型算法的基础是迭代器. 迭代器令算法不依赖于容器,但是算法依赖于元素类型的操作.也即:算法永远不会执行容器的操作. 那么,如果想向容器中添加元素或者执行其他的一些操作呢?标准库提供了插入迭代器来完成 ...
- 2015-10-27 js
1.声明变量: 2.prompt属性的使用: prompt("提示框的标题","提示框的输入提示内容"); prompt的调用结果就是他输入框内的内容!!! 3 ...
- Linux 下C++编写
今天搞了一天Linux下C++编程,还没有什么成效.好烦躁好心焦,想砸电脑的冲动.抽根烟理下思路一定要把它拿下!! ===搞了两天,真是搞到生无可恋,试了共享文件, 试了网络配置,各种博客就是各种行不 ...