为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语言添加一个对象系统的更多相关文章

  1. deb包+软件图标+添加到系统菜单+举例安装卸载

    本文介绍的内容和实验一下: 1. 制造deb包.2. 为了使软件图标.3. 开始菜单中添加到系统中的软件:4. 安装和卸载制作的deb包. 1. 制作deb包 制作deb包的方法可能有多种,本文使用的 ...

  2. 在Jekyll博客添加评论系统:gitment篇

    最近在Github Pages上使用Jekyll搭建了个人博客( jacobpan3g.github.io/cn ), 当需要添加评论系统时,找了一下国内的几个第三方评论系统,如"多说&qu ...

  3. C语言操作WINDOWS系统存储区数字证书相关函数详解及实例

     C语言操作WINDOWS系统存储区数字证书相关函数详解及实例 以下代码使用C++实现遍历存储区证书及使用UI选择一个证书 --使用CertOpenSystemStore打开证书存储区. --在循环中 ...

  4. C语言如何向系统接要存

    C语言如何向系统接要存,就有这么三种方式: 1.向栈要. 2.向堆要. 3.向数据段要. 这一下就扯出了三种内存空间,内存空间的本质是一样的,一个地址对应一个方框,方框里可以放数据.但是为了更好的去 ...

  5. Office 2010 安装程序包的语言不受系统支持

    主要看了这篇文章之后让我有了处理思路. 最后我直接用压缩文件进行安装,没有时行解压.这样就不会出现找不到什么文件的问题了.所以语言不受系统支持问题也就解决了. 原文内容: 前几天,有位好友跟我说他的 ...

  6. 干货 | 携程多语言平台-Shark系统的高可用演进之路

    https://mp.weixin.qq.com/s/cycZslUlfyVNm2GVrZm1Cw 干货 | 携程多语言平台-Shark系统的高可用演进之路 原创 Fenlon 携程技术 2020-1 ...

  7. Autolayout-VFL语言添加约束

    一.VFL语言简洁 VFL(Visual format language)语言是苹果为了简化手写Autolayout代码所创建的专门负责编写约束的代码.为我们简化了许多代码量. 二.使用步骤 使用步骤 ...

  8. Autolayout-VFL语言添加约束-备

    一.VFL语言简介 VFL(Visual format language)语言是苹果为了简化手写Autolayout代码所创建的专门负责编写约束的代码.为我们简化了许多代码量. 二.使用步骤 使用步骤 ...

  9. R语言图形base系统(一)

           一般R作图有三大绘图系统:base系统.ggplot2绘图系统.lattice绘图系统.        本篇主要介绍base系统绘图时的图形参数.一般用plot()函数来完成.在R中,若 ...

随机推荐

  1. Activiti 多个并发子流程的应用

    多个部门发起资金计划,最后统一到财务部审批,每个部门发起资金计划是一个子流程,财务部审批是多个部门的计划同时审批,审批完成后,再提交上级领导审批. 流程如下: 要解决以上问题,需要实现多个子流程并行处 ...

  2. mysql在update时,从其他select结果集更新表

    需要使用join,例如 select g.id,g.res_count,count(gr.r_id) cnt from mb_game_res gr left join mb_game g on gr ...

  3. 【英语】Bingo口语笔记(46) - 不可能的表达

  4. C++调试 输出数组内容和数组名

    #include <cstdio> using namespace std; //函数定义 #define printArr(arr,n,format) \ printf("%s ...

  5. 【转】谈一谈PHP字串清除空格函数不安全

    清除空格的方法是不安全的,部分原因是因为字符中的空格非常多,例如 "addslashes的问题在 于黑客 可以用0xbf27来代替单引号,而addslashes只是将0xbf27修改为0xb ...

  6. Modularity模块化

    Modularity in this context refers to test scripts, whereas independence refers to test cases. Given ...

  7. php动态生成一个xml文件供swf调用

    <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdo ...

  8. [转]Chrome浏览器的离线安装包下载地址

    每当chrome有更新之后,都有不少用户想要下载离线版的安装文件,但苦于找不到下载地址而发愁,其实这个问题很简单,下面我来分享一下方法(仅针对Windows操作系统): 对于稳定版(正式版)Chrom ...

  9. 横版动作MOBA《超宇宙》首测试玩曝光 详解游戏特色(转)

    http://play.163.com/15/0911/11/B37RHHO100314J6L.html

  10. 安装linux操作系统--浪潮服务器

    一直都是在虚拟机上进行安装linux操作系统,在服务器上安装的很少,也没有碰到过没找到驱动的情况,例如什么raid卡驱动,网卡驱动等异常情况的发生. 这次安装了两台服务器,浪潮的提供的服务器,硬盘是两 ...