Object-Oriented Programming in C

written by Trevis Rothwell (in 2006)

Home : Writing : One Article


Introduction

I find myself converting old Ada software to C (for business, not pleasure), and many memories of my times spent with C have resurfaced. I'm a bit wiser now, as Lisp makes a software engineer wise, and thought that I should take some time to jot down some basic ideas about building object-oriented software using C. Whether if you are building new C software from scratch, or rebuilding some old software afresh in C, I hope that this little guide will prove beneficial for you. I will continue to add to it as I think of things to add. Comments and corrections are welcome via email.

Objects and Classes

C is not an object-oriented language. You might rightly wonder, then, how can we build object-oriented software using C? The best answer to this question is, of course, is that you should use C++ or Java or some other language better suited to the problem you need to solve, but this is not always an option.

Since C has no built-in support for objects, we will have to make do with what it does offer. The closest thing to the C++ class that we have in C is a structure. Here's an example of one:

struct cameraShot
{
  int filmSpeed;
  int focalLength;
  float aperture;
  float shutterSpeed;
  char description[100];
};

This defines a new type called struct cameraShot, and is kind of like a class. We can now make new variables that are "instances" of this type, and are kind of like objects:

struct cameraShot petLlamaInHelicopter =
  {400, 135, 2.8, 0.01, "My pet llama on board a helicopter."};

struct cameraShot petLlamaSurfing = 
  {800, 300, 2.8, 0.01, "My pet llama surfing in southern California."};

We can easily write functions that operate on these objects. We can't declare the function inside of the structure, so we have to pass a pointer to an instance of the structure as a parameter to the functions:

int getFilmSpeed (struct cameraShot *cs)
{
  return cs->filmSpeed;
}

That wasn't really that bad, was it? Let's take a look at a few more:

void setFilmSpeed (struct cameraShot *cs, int filmSpeed)
{
  cs->filmSpeed = filmSpeed;
}
void setDescription (struct cameraShot *cs, char *description)
{
  strcpy (cs->description, description);
}

It's a little bit messier than C++, but not a lot. The big problem you are likely to run into is with the function names. In C++, a function like setFilmSpeed would be hidden inside the class definition, tucked away into its own little namespace. In C, it's a global function declaration that potentially could be seen by the rest of the program. You can't have more than one function named setFilmSpeed, so you'll probably want to be a little more clever with your naming conventions to ensure that you won't trample on a fellow programmer's identifiers, nor he on yours.

Generic Programming

C is not a very good language for generic (a.k.a., template) programming. If you're familiar with templates in C++ or generics in Java or Ada, or if you've simply been coding in Lisp your whole life and don't think much about types at all, then you might find yourself stumped at how to perform some of your cool coding techniques in C.

The traditional way in which C pretends to offer generic programming is through void pointers. A void pointer can be used to point to any kind of data object C. That's the good news. The bad news is that in order to really use the data object, you still have to know the type of the object. There are some limited things you can do with the data without knowing its type, but somewhere down the line, unless you're just moving raw data around, you're going to have to know the type.

If you only have to deal with a finite (and preferably small) number of possible types for your generic, then you can get away with having the function accept a void pointer along with some sort of type indicator. For example:

enum genericTypes {integer, floatingPoint};

void addNumbers (void * first, void * second, enum genericTypes type)
{
  if (type == integer)
  {
    int *a = (int*)first;
    int *b = (int*)second;
    *a = *a + *b;
  }
  else if (type == floatingPoint)
  {
    float *a = (float*)first;
    float *b = (float*)second;
    *a = *a + *b;
  }
}

Whew. That'd be tedious for complex operations with many possible types, but it could be done.

Additional Resources

Much has been written about C. Not a whole lot of in-depth material about C has made it's way to the World Wide Web, probably because most programmers really do cry when they think about C, and lack motivation to write about it for free distribution. Nevertheless, here are some sources you might find useful, both online and in hardcopy:



tjr@gnu.org