为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. LeetCode Reverse Nodes in k-Group 每k个节点为一组,反置链表

    题意:给一个单链表,每k个节点就将这k个节点反置,若节点数不是k的倍数,则后面不够k个的这一小段链表不必反置. 思路:递归法.每次递归就将k个节点反置,将k个之后的链表头递归下去解决.利用原来的函数接 ...

  2. android 呼入电话的监听(来电监听)转

    需要权限: <uses-permission android:name="android.permission.READ_PHONE_STATE" /> 方式一:通过广 ...

  3. Linux Shell编程(5):整数运算

    http://blog.sina.com.cn/s/blog_6db275da0101asmf.html #!/bin/sh let a=$1+$2 b=$[$1+$2] ((c=$1+$2)) d= ...

  4. Heritrix源码分析(十一) Heritrix中的URL--CandidateURI和CrawlURI以及如何增加自己的属性(转)

    本博客属原创文章,欢迎转载!转载请务必注明出处:http://guoyunsky.iteye.com/blog/649889 本博客已迁移到本人独立博客: http://www.yun5u.com/ ...

  5. nodejs 改变全局前缀

    npm的包安装分为本地安装(local).全局安装(global)两种,从敲的命令行来看,差别只是有没有-g而已,比如: 复制代码 代码如下: npm install grunt # 本地安装npm ...

  6. base64 encoding

    //https://en.wikipedia.org/wiki/Base64 std::string base64Encode(const std::vector<char>& b ...

  7. Oracle查看和修改其最大的游标数

    原文 Oracle查看和修改其最大的游标数 以下的文章主要是介绍Oracle查看和修改其最大的游标数,本文主要是通过相关代码的方式来引出Oracle查看和修改其最大的游标数的实际操作步骤,以下就是文章 ...

  8. [Everyday Mathematics]20150221

    设 $y_n=x_n^2$ 如下归纳定义: $$\bex x_1=\sqrt{5},\quad x_{n+1}=x_n^2-2\ (n=1,2,\cdots). \eex$$ 试求 $\dps{\vl ...

  9. duilib让不同的容器使用不同的滚动条样式

    装载请说明原出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/42240569 以前在给一个容器设置横纵向的滚动条时,一直是通过设置xml ...

  10. Pig Run on Hadoop, V1.0

    ——安装hadoop参考这篇blog: http://www.cnblogs.com/lanxuezaipiao/p/3525554.html?__=1a36 后面产生的问题,slave和master ...