为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. 纯CSS3大转盘抽奖(响应式、可配置)

    源于前段时候微信小程序最初火爆公测时段,把以前用 Canvas 实现的大转盘抽奖移植成微信小程序,无奈当时小程序对 Canvas 支持不够完善,只好降低用 CSS3 实现.虽然比不上 Canvas 绘 ...

  2. nanakon

    1.安装python pip3 install tornado pip3 install pymysql pip3 install qiniu pip3 install pillow 2.安装mysq ...

  3. 最大熵模型 Maximum Entropy Model

    熵的概念在统计学习与机器学习中真是很重要,熵的介绍在这里:信息熵 Information Theory .今天的主题是最大熵模型(Maximum Entropy Model,以下简称MaxEnt),M ...

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

  5. JVM内存结构——运行时数据区

    在Java虚拟机规范中将Java运行时数据划分为6种,分别为: PC寄存器(程序计数器) Java栈 堆 方法区 运行时常量池 本地方法栈 一.PC寄存器(程序计数器) PC寄存器(Program C ...

  6. jQuery基础知识--Form基础(续)

    下拉框应用 <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF ...

  7. Linux makefile教程之使用变量五[转]

    使用变量 ———— 在 Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使 用的地方.其与C/C++所不同 ...

  8. 使用Yii框架自带的CActiveForm实现ajax提交表单

    Php代码:  <div class="form"> <?php $form=$this->beginWidget('CActiveForm', array ...

  9. winform 下log4net简单应用示例及“缺少log4net引用”的处理方案

    1.添加应用log4net.dll 2.新增log4net.config文件,文件内容如下 <?xml version="1.0" encoding="utf-8& ...

  10. Visual Studio 2010+Oracle 10g +NHibernate配置

    南京酷都面试,考官问:你知道NHibernate吗?瞬间我就急了:只听说过Hibernate,NHibernate是什么?还有其他问题也是不知道,所以后果就悲剧了. 自己做一个小系统,总是想如果数据量 ...