Lab 14: Forcing C to be Object-Oriented

...and then [Men] would or could be made to mate with Orcs, producing new breeds, often larger and more cunning. There is no doubt that long afterwards, in the Third Age, Saruman rediscovered this, or learned of it in lore, and in his lust for mastery commited this, his wickedest deed: the interbreeding of Orcs and Men, producing both Men-orcs large and cunning, and Orc-men treacherous and vile.
--- J.R.R. Tolkien, Morgoth's Ring

How can we tolerate these indignities [being forced to program in Fortran or C equivalents because more creative programming languages are supressed]? The frustratingly simple answer is the universal nature of programming languages---the fact that one can program in any one of them what can be programmed in any other. We are able to clever our way out of any programming box. The more difficult the task, the more pride we can take in the accomplishment.
--- Richard Gabriel and Ron Goldman, "Mob Software: The Erotic Life of Code"

The goal of this lab is to practice using function pointers to emulate in C the object oriented features of encapsulating (data and functionality together), subtyping, and polymorphism.

1. Introduction

The first C++ compilers were translated C++ programs to C source, which was then fed into a C compiler. Also during the early days of Java, someone bothered to write a Java-to-C compiler (rather than compiling Java to bytecode/classfiles).

How is a translation like this possible, since C lacks so many of the core elements of a language like Java? For one thing, C's programming concepts aren't much different from how the computer (at the hardware level) actually works--- and obviously Java programs can be run on a real computer. Besides, the Church-Turing thesis tells us that all models of computing are equivalent to each other.

As we began to imagine in class yesterday, function pointers help a lot for doing something in C that looks like polymorphism. (In fact, it is polymorphism, just not subtype polymorphism.) In this lab, we will fill out the details.

The example we are going to work on comes from one of the first examples of subtyping and dynamic dispatch I use in CS 235. Suppose we need to model several kinds of mathematical functions (polynomial, step, exponential...), each of which needs to provide methods for evaluation, differentiation, and integration. To implement this, we have a Function interface implemented by Polynomial, Step, and Exp classes. Look over the handout to familiarize yourself with this.

2. Set up

Copy the following files from course public directory.

cp /homeemp/tvandrun/pub/245/cfun/oo/* .

3. function.h

Open function.h. This is equivalent to Function.java in that its purpose is to define a type according to a set of operations, like an interface. Open functiondriver.c and notice how this type is used.

As we know, C's facility for making new types is the struct. Structs cannot have behavioral members (ie, methods), but we can emulate methods by letting it have function pointers. A circle type, for example, which would contain methods for computing the circumerence and area, might look like

struct circle_t
{
    double radius;
    double (*circumference) (struct circle_t * this);
    double (*area) (struct circle_t * this);
};

typedef struct circle_t circle;

Note that the functions circumference() and area() have a circle * parameter. That's because otherwise the function has no reference to the "receiver" of the "method call." We need to pass that reference as a parameter (which is why I call that parameter this). This is actually a realistic "translation" of what's going behind the scenes during method dispatch in Java; in Java bytecode, for example, all non-static methods have one extra parameter for passing a reference to the receiver.

We would then need to write functions to serve for circumference() and area().

double circumference_c(circle* this)
{
   return 3.14159 * 2 * this->radius;
}

double area_c(circle* this)
{
   return 3.14159 * this->radius * this->radius;
}

Finally, a constructor-like function would create an "instance."

circle* newCircle(double radius)
{
    circle* toReturn = (circle*) malloc(sizeof(circle));
    toReturn->circumference = circumference_c;
    toReturn->area = area_c;
    toReturn->radius = radius;
    return toReturn;
}

But we have an extra problem: We don't know what data member function should have, since that will vary among the subtypes. To handle this, we give struct function_t a field data of type void *, which is C's way of saying "pointer to a value of an unknown type" (compare with Java's Object class). We will set data to refer to structs polynomial, step, and exponential.

Two more things about function.h: First, it also has a function pointer called destroy, to refer to a function that deallocates the structure. Second, the #ifndef FUNCTION ... #endif stuff prevents this file from being included more than once.

4. Polynomial

Inspect the type definition for polynomial in polynomial.h, and understand how the implementation of an evaluate function works in polynomial.c. For convenience (to avoid lots of casts), you will probably want almost all of the functions you write to day to include a line like

  polynomial* data = (polynomial*) this->data;

Finish this file-- write the other functions, including the constructor-like newPolynomial. Some hints:

Then compile and test using the given driver.

5. Step and exponential

Then make types for step functions and exponential functions in appropriate files. Uncomment the appropriate lines in the driver to test them.

For the exponential type, you will probably want to use the standard function pow(), which works just like Java's Math.pow(). You'll need to include math.h, and with you do your final compilation, you'll need to use the -lm flag, which links the math library. E = 2.718281828459045

6. Turn in

Turn in a hardcopy of the classes you wrote.


Thomas VanDrunen
Last modified: Wed Apr 4 14:13:17 CDT 2007