为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. 凸优化简介 Convex Optimization Overview

    最近的看的一些内容好多涉及到凸优化,没时间系统看了,简单的了解一下,凸优化的两个基本元素分别是凸函数与凸包 凸集 凸集定义如下: 也就是说在凸集内任取两点,其连线上的所有点仍在凸集之内. 凸函数 凸函 ...

  2. string tips

    1. .net文档中说,split 比 indexOf 消耗更多的性能.在as3中经过本人测试,这个结论一样成立.而且,字符串越长,split和indexOf的差距就越明显!!所以,能用indexOf ...

  3. python开发学习-day01 (python安装与版本、字符串、字典、运算符、文件)

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  4. 2016第20周四java基础概念

    简单的说JDK=JRE+Java编译器.调试器.工具类库等:JRE=JVM(类似于jre目录下的bin)+必要运行的类库(类似于jre目录下的lib) JVM:Java Virtual Mechina ...

  5. 一种Javascript解释ajax返回的json的好方法

    通常ajax请求返回的格式为json或者xml,如果返回的是json,则可以通过转换成javascript对象进行操作,如下: 1.ajax请求的controller实现 @RequestMappin ...

  6. ios import和@class的区别

    二者的区别在于: 1.import会包含这个类的所有信息,包括实体变量和方法,而@class只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑,后面会再告诉你. 2.在头 ...

  7. 热门Web开发方式 REST实现原理浅析

    REST 首先只是一种架构样式,不是一种标准.这点和 Ajax 类似,两者都是利用现有的成熟技术.在 REST 的定义中,一个 Web 应用总是使用固定的 URI 向外部世界呈现(或者说暴露)一个资源 ...

  8. window 7 下一台cp 两个mysql 配置主从

    环境 : 个人 pc windows7 一台 ; 使用 : 官方下载: mysql-noinstall-5.5.11-win32.zip 1. 解压 成2个 (文件夹) mysql_master (主 ...

  9. Go 语言做的几个验证码

    1.http://www.oschina.net/code/snippet_173630_12006 : 效果: 源代码: 1: package main 2:  3: import ( 4: cra ...

  10. linux实现自动远程备份(scp+ssh)

    刚上线的服务器需要备份日志,要备份到另一台服务器上去,为了减少工作量,采用linux的定时任务去自动执行.因服务器都是linux的,因此采用linux的远程复制scp命令.但这里涉及到一个问题,就是s ...