views:

1668

answers:

16

Assuming I have to use C (no C++ or object oriented compilers) and I don't have dynamic memory allocation, what are some techniques I can use to implement a class, or a good approximation of a class? Is it always a good idea to isolate the "class" to a separate file? Assume that we can preallocate the memory by assuming a fixed number of instances, or even defining the reference to each object as a constant before compile time. Feel free to make assumptions about which OOP concept I will need to implement (it will vary) and suggest the best method for each.

Restrictions:

  • I have to use C and not an OOP because I'm writing code for an embedded system, and the compiler and preexisting code base is in C.
  • There is no dynamic memory allocation because we don't have enough memory to reasonably assume we won't run out if we start dynamically allocating it.
  • The compilers we work with have no problems with function pointers
+1  A: 

My approach would be to move the struct and all primarily-associated functions to a separate source file(s) so that it can be used "portably".

Depending on your compiler, you might be able to include functions into the struct, but that's a very compiler-specific extension, and has nothing to do with the last version of the standard I routinely used :)

warren
Function pointers are all good. We tend to use them to replace large switch statements with a lookup table.
Ben Gartner
A: 

C isn't an OOP language, as your rightly point out, so there's no built-in way to write a true class. You're best bet is to look at structs, and fucntion pointers, these will let you build an approximation of a class. However, as C is procedural you might want to consider writing more C-like code (i.e. without trying to use classes).

Also, if you can use C, you can probally use C++ and get classes.

Peter
+2  A: 

In your case the good approximation of the class could be the an ADT. But still it won't be the same.

Artem Barger
Can anyone give a brief diff between an abstract data type and a class? I've always of the two concepts as closely linked.
Ben Gartner
They are indeed closely related. A class can be viewed as an implementation of an ADT, since (supposedly) it could be replaced by another implementation satisfying the same interface. I think it is hard to give an exact diff though, as the concepts aren't clearly defined.
Jørgen Fogh
+18  A: 

That depends on the exact "object-oriented" feature-set you want to have. If you need stuff like overloading and/or virtual methods, you probably need to include function pointers in structures:

typedef struct {
  float (*computeArea)(const ShapeClass *shape);
} ShapeClass;

float shape_computeArea(const ShapeClass *shape)
{
  return shape->computeArea(shape);
}

This would let you implement a class, by "inheriting" the base class, and implementing a suitable function:

typedef struct {
  ShapeClass shape;
  float width, height;
} RectangleClass;

static float rectangle_computeArea(const ShapeClass *shape)
{
  const RectangleClass *rect = (const RectangleClass *) shape;
  return rect->width * width->height;
}

This of course requires you to also implement a constructor, that makes sure the function pointer is properly set up. Normally you'd dynamically allocate memory for the instance, but you can let the caller do that, too:

void rectangle_new(RectangleClass *rect)
{
  rect->width = rect->height = 0.f;
  rect->shape.computeArea = rectangle_computeArea;
}

If you want several different constructors, you will have to "decorate" the function names, you can't have more than one rectangle_new() function:

void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
  rectangle_new(rect);
  rect->width = width;
  rect->height = height;
}

Here's a basic example showing usage:

int main(void)
{
  RectangleClass r1;

  rectangle_new_with_lenghts(&r1, 4.f, 5.f);
  printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1));
  return 0;
}

I hope this gives you some ideas, at least. For a successful and rich object-oriented framework in C, look into glib's GObject library.

unwind
Not having had to try to write object-oriented C, is it usually best to make functions that take `const ShapeClass *` or `const void *` as arguments? It would seem that the latter might be a bit nicer on inheritance, but I can see arguments both ways...
Chris Lutz
@Chris: Yeah, that's a hard question. :| GTK+ (which uses GObject) uses the proper class, i.e. RectangleClass *. This means you often have to do casts, but they provide handy macros do help with that, so you can always cast BASECLASS *p to SUBCLASS * using just SUBCLASS(p).
unwind
+1  A: 

Do you want virtual methods?

If not then you just define a set of function pointers in the struct itself. If you assign all the function pointers to standard C functions then you will be able to call functions from C in very similar syntax to how you would under C++.

If you want to have virtual methods it gets more complicated. Basically you will need to implement your own VTable to each struct and assign function pointers to the VTable depending on which function is called. You would then need a set of function pointers in the struct itself that in turn call the function pointer in the VTable. This is, essentially, what C++ does.

TBH though ... if you want the latter then you are probably better off just finding a C++ compiler you can use and re-compiling the project. I have never understood the obsession with C++ not being usable in embedded. I've used it many a time and it works is fast and doesn't have memory problems. Sure you have to be a bit more careful about what you do but its really not that complicated.

Goz
+2  A: 

Use a struct to simulate the data members of a class. In terms of method scope you can simulate private methods by placing the private function prototypes in the .c file and the public functions in the .h file.

Taylor Leese
+5  A: 

I had to do it once too for a homework. I followed this approach:

  1. Define your data members in a struct.
  2. Define your function members that take a pointer to your struct as first argument.
  3. Do these in one header & one cpp. Header for struct definition & function declarations, cpp for implementations.

A simple example would be this:

/// Queue.h
struct Queue
{
    /// members
}
typedef struct Queue Queue;

void push(Queue* q, int element);
void pop(Queue* q);
// etc.
///
erelender
This is what I've done in the past, but with the addition of faking scope by placing function prototypes in either the .c or .h file as needed (as I mentioned in my answer).
Taylor Leese
I like this, the struct declaration allocates all the memory. For some reason I forgot this would work well.
Ben Gartner
I think you need a `typedef struct Queue Queue;` in there.
Craig McQueen
Or just typedef struct { /* members */ } Queue;
Brooks Moses
#Craig: Thanks for the reminder, updated.
erelender
Nicely worded... concise and to the point.
blak3r
+5  A: 

If you only want one class, use an array of structs as the "objects" data and pass pointers to them to the "member" functions. You can use typedef struct _whatever Whatever before declaring struct _whatever to hide the implementation from client code. There's no difference between such an "object" and the C standard library FILE object.

If you want more than one class with inheritance and virtual functions, then it's common to have pointers to the functions as members of the struct, or a shared pointer to a table of virtual functions. The GObject library uses both this and the typedef trick, and is widely used.

There's also a book on techniques for this available online - Object Oriented Programming with ANSI C.

Pete Kirkham
Cool! Any other recommendations for books on OOP in C? Or any other modern design techniques in C? (or embedded systems?)
Ben Gartner
A: 

The first c++ compiler actually was a preprocessor which translated the C++ code into C.

So it's very possible to have classes in C. You might try and dig up an old C++ preprocessor and see what kind of solutions it creates.

Toad
That would be `cfront`; it ran into problems when exceptions were added to C++ - handling exceptions is not trivial.
Jonathan Leffler
+3  A: 

you can take a look at GOBject. it's an OS library that give you a verbose way to do an object.

http://library.gnome.org/devel/gobject/stable/

Alex
Very interested. Anyone know about the licensing? For my purposes at work, dropping an open source library into a project probably isn't going to work from a legal standpoint.
Ben Gartner
GTK+, and all libraries that are part of that project (including GObject), are licensed under the GNU LGPL, which means you can link to them from proprietary software. I don't know if that is going to be feasible for embedded work, though.
Chris Lutz
+1  A: 

My strategy is:

  • Define all code for the class in a separate file
  • Define all interfaces for the class in a separate header file
  • All member functions take a "ClassHandle" which stands in for the instance name (instead of o.foo(), call foo(oHandle)
  • The constructor is replaced with a function void ClassInit(ClassHandle h, int x, int y,...) OR ClassHandle ClassInit(int x, int y,...) depending on the memory allocation strategy
  • All member variables are store as a member of a static struct in the class file, encapsulating it in the file, preventing outside files from accessing it
  • The objects are stored in an array of the static struct above, with predefined handles (visible in the interface) or a fixed limit of objects that can be instantiated
  • If useful, the class can contain public functions that will loop through the array and call the functions of all the instantiated objects (RunAll() calls each Run(oHandle)
  • A Deinit(ClassHandle h) function frees the allocated memory (array index) in the dynamic allocation strategy

Does anyone see any problems, holes, potential pitfalls or hidden benefits/drawbacks to either variation of this approach? If I am reinventing a design method (and I assume I must be), can you point me to the name of it?

Ben Gartner
As a matter of style, if you have information to add to your question, you should edit your question to include this information.
Chris Lutz
You seem to have moved from malloc dynamically allocating from a large heap to ClassInit() dynamically selecting from a fixed size pool, rather than actually doing anything about what will happen when you ask for another object and don't have the resources to provide one.
Pete Kirkham
Yes, the memory management burden is shifted onto the code calling the ClassInit() to check that the returned handle is valid. Essentially we've created our own dedicated heap for the class. Not sure I see a way to avoid this if we want to do any dynamic allocation, unless we implemented a general purpose heap. I'd prefer to isolate the risk inherit in the heap to one class.
Ben Gartner
A: 

There's a very extensive book on the subject, which might be worth checking out:

Object Oriented Programming in ANSI-C

Ruben Steins
+2  A: 

Also see this answer and this one

It is possible. It always seems like a good idea at the time but afterwards it becomes a maintenance nightmare. Your code become littered with pieces of code tying everything together. A new programmer will have lots of problems reading and understanding the code if you use function pointers since it will not be obvious what functions is called.

Data hiding with get/set functions is easy to implement in C but stop there. I have seen multiple attempts at this in the embedded environment and in the end it is always a maintenance problem.

Since you all ready have maintenance issues I would steer clear.

Gerhard
+2  A: 

Miro Samek developed an object-oriented C framework for his state machine framework: http://sourceforge.net/projects/qpc/. And he also wrote a book about it: http://www.state-machine.com/psicc2/.

Frank Grimm
A: 

Here is a whitepaper on the subject form ARTiSAN Software. It is primarily intended as a guide to implementing UML OO designs in C. It is reasonably practical and pragmatic.

Clifford
+1  A: 

C Interfaces and Implementations: Techniques for Creating Reusable Software, David R. Hanson

http://www.informit.com/store/product.aspx?isbn=0201498413

This book does an excellent job of covering your question. It's in the Addison Wesley Professional Computing series.

The basic paradigm is something like this:

/* for data structure foo */

FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);
Mark Harrison