Wednesday, September 19, 2007

Object Oriented C

Or "C for Java Programmers"...

Revision History Revision 0: (19 September 2007) Published. Revision 1: (19 September 2007) Modified the dummy header.

Introduction

A class, in the immortal words of Dr. Neat, is defined by three things: 1) constructor(s), 2) fields, and 3) methods.

Since this tutorial is written for Java programmers, I will focus on the implementation of classes in C by using structs, function pointers, etc.

One might ask "Why should I bother learning C since C++ is so much more convenient for me?" Well, that's a valid question a Java programmer may ask. The answer is that C, even object oriented C, is more sparing on the resources of the target machine.

So if you are doing embedded programming, you would want to opt for C rather than C++. If you are doing, e.g., generic programming of a calculator, or a shell, or something, you might want to use C++ instead.

But if one is programming on a machine with one megabyte of RAM and a 300 MHz processor, C is your language of choice.

Objects, Objects, Objects, and Objects

The inspiration of object oriented C comes from the virtual file system implementation on SunOS 2.0 from 1985 (see the immortal technical paper that describes the virtual file system implementation).

What we do is use structs as the object, function pointers as the methods, and - because we are using structs - the fields are already taken care of.

There are two approaches one can take: one is to follow the orthodox vnode approach and have a struct for the fields and a struct for the operations, or follow the lazy approach and use one struct.

Consider the following object:
1. struct object {
2.       int id;
3.       void (*dumpState)(struct object *this);
4.       bool (*equals)(struct object *this, struct object *o);
5.       char* (*toString)(struct object *this);
6. };
Line 2 is the field of the object class, lines 3-5 are the methods of the class. Note how the first argument of all the methods is the this pointer.

This is similar to Python's object oriented approach.

The alternate approach would be to do the following:
01. struct object {
02.       int id;
03.       struct obj_ops *ops;
04. };
05.
06. struct obj_ops {
07.       void (*dumpState)(struct object *this);
08.       bool (*equals)(struct object *this, struct object *o);
09.       char* (*toString)(struct object *this);
10. };
Note how the operations are encapsulated in one struct and the fields are in the other. This is the orthodox approach.

The problem with this approach is that if one wants to get the object's toString() method, the line of code would look like: obj.ops->toString(obj); as opposed to a simpler obj.toString(obj).

The UNIX KISS

This may be impresive to some, but I'm sure someone has said "Hey, if the toString() method takes in an struct object object anyways, why not just use the line of code toString(obj)?"

That is actually what Unix did. So if we were to take a renewed look at the list of operations and the structure, what we would do is in a hypothetical header write:
/* object.h header file */

struct object {
      int id;
};

void dumpState(struct object *this);
bool equals(struct object *this, struct object *o);
char* toString(struct object *this);

/* end of the header file */
Then the "methods" of the class would be implemented by simply calling the function and making the first argument the callee.

1 comment:

Anonymous said...

So what's happened to your quest to learn some physics?

Myke.