Object Orientation in C?!

Introduction

If you ever had to write code for a microprocessor or any other device which had no C++ compiler or you had to use C and not C++, you may value the layer of abstraction and structure classes provide. This project will show you that this is also possible in C. I for instance always wanted to use strings and lists in C without spreading new methods everywhere. Objects should have properties, methods and also should support polymorphism (calling different methods by the same name depending on the type).

Turns out you can do that in C by defining pointers in a struct and setting them to certain methods inside a constructor. You can even pass around any object since any struct can be passed as a void pointer.

If you just want to try it out now, feel free to test the demo or download the source code.

Background

C++ started out in 1979 by Bjarme Stroustrup. At first C++ (C with classes) started out in normal C. As you will see, it is totally possible to create class like objects and work with them in C. C++ was (at the beginning at least) a convenience update for the object oriented paradigm.

Object orientation in C has some drawbacks: You have to set every method manually, private and public members are realised by using c and h files.

You will also see that polymorphism and generic template like programming is possible in C. Once you have created a class in C, you really can use it on any machine.

The constructor returns a pointer to the instance of the class. Every method like Append, Free_String (destructor) is defined in the c file above the constructor. You can see that for every instance of string,the append pointer is set to the same method. It is really important to define a constructor and to clean up for every malloc used.

Notice that Free also cleans up itself. No leaks are left behind.
Note that the Length property is updated after every append. This class can be expanded easily by defining a method in the .h file and the code in the .c file. Dont forget to "wire" the function pointer of the .h file to the function pointer of the .c file.

(private members) (List.c)

You can see that the class implementation is similar to String. You set all corresponding methods in the constructor. This list is typesafe as you can only insert integers. You could also insert the pointer to any data (32 bit only). At first, the list has space for 16 elements. If you add more than 16 elements, the internal array gets resized to hold 64 elements and so on. The implementation is pretty much the same as in List<T> in the .NET Framework.

The Count property always has the right value and internally the list is very efficient because it is just an array.

(Main.c)

This program creates a list with 100000 elements. Then it deletes the third element. The shifting of the array is as fast as it can be by using memshift. A linked list would be faster for this, but this list does not waste memory space by saving a pointer to the next element.

string.h

For the constructor, all we have to do is wire the method (print) to another method. Both string types will look the same from a usage point of view as all methods have the same name. Internally, different methods get called. string.h really just defines an interface with which we can do anything internally really.

Templates (Generics) in C

This is a little trickier. You see it has a good preprocessor. #define inserts your snippet anywhere it is used. If we want a generic list, all we have to do is to #define a generic struct. The ##T keyword does get us the string passed to define. You can also make multi line #defines by ending the line with \.

This is really useful because you don't have to implement a list int, float, double and so on. With this, you don't lose type safety.Warning: Although this is type safe, bugs induced by the preprocessor can be hard to detect. It is easier to just use the normal list of integers with pointers for generic lists.

This only inserts the interface for the generic list. For the implementation, you have to use yet another preprocessor macro:

Comments and Discussions

I did laugh that a lot of people gave you flack for doing it with strings ... I am sure you are aware you would never do it with strings but larger objects

Commercially there are some adjustments to your idea. Lets deal with the first major ones which are usually the first two entries in the object struct namely object id and a self pointer. I will use your strings as an example, ignore we know we would never do this with strings.

You do this so you can internally hide data via the self pointer, that is the public interface declared in your .h file
In the .c file you declare a new private struct which incorporates that public struct

Do you see the trick here, self is a void* it is untyped, the public interface can be in any larger object just like it is for our private version. It's actually a sub interface and that gives you the tricks to do polymorphism. Lets look at the modified print function

So lets detail what changed
1.) It allowed me have private data (Content and length) cant be referenced in any way by the .h interface.
2.) The public interface structure and the object memory can be physically different places
3.) Every object interface has a self pointer, you can have two public object interfaces in another larger object structure

To show you it is very different to your code consider this

// Lets dereference your constructor into a struct (it copies the public interface)String p = *New_String("Hello");
// Lets show you what happened above
String* q = &p; // Debug here// Look at the values of q and p->self// The interface structure p is totally divorced from the allocated memory// Walk into the next line with a debugger and check what happens
p.Print(&p); // Our divorced interface will still operate correctly// Now lets copy object string p to r ... YOUR VERSION WILL DUPLICATE THE STRING POINTER p.ContentString r = p;
// Lets append some text to r ... YOUR VERSION r.Content & r.Length change but they don't in p// The content pointer in p will in fact be invalid after this call
r.Append(&r, " there");
// Now check p still reflects r as they are the same object ... YOUR VERSION WONT p & r differ and it will crash
p.Print(&p);
// Check our held pointer q still works
q->Print(q);
// The modified code will pass all the above tests it truely behaves like an Object

Do you see that your object memory interface and your object memory are no longer the same. The object interface can reside anywhere, inside another object etc and that structure can even be a different size to your interface structure. The ObjId is a sanity check to make sure we are the correct object type when we transfer the self pointer. There is a more secure trick that is sometimes used in that the self pointer is made a longlong (64 bits) and the upper 32 bits was the objId and the lower 32 bits the self pointer proper. It's further protection to make sure the self pointer which is pointing to an object can be guaranteed to be of that type of object and programmers couldn't mess it up. It's vital the ObjId and the self pointer remain paired always.

So the key thing you haven't managed to do in your code is seperate the interface structures from the object data itself and so it doesn't really behave like a proper object.

Wow thanks so much. This is really interesting input. I have also noticed that if we define a generic struct called Object and replace the typecode with it, we can have an (Object) cast which is allowed for every "class" defined this way. The Object struct could contain the Name of the type and other metadata known at compile time. This way runtime reflection in c would be possible

Thanks for sharing.
Maybe I will add a section to this article called "The right way" if you allow?

A past employer produces a commercial C API, they used to produce a few different modules that had common methods, i.e. polymorphism. So there was a large struct full of function pointers that the different modules would initialise with the specific implementation. Personally I preferred C++ so left to do other things.

Obviously, it is possible to do object programming in C and your method is straight-forward, but naive.

If, as you say, your target is microcontrollers, then duplicating pointers to methods in each object instance is way wasteful (each your String instance carries around about 16 bytes of duplicate data).

There are many existing object oriented C based (or C compatible) libraries like GTK or Windows COM which do this in more efficient way. You can look to them for inspiration.

Well of course I could outsource all pointers to a single struct. Since all methods are static anyway. This way you do not have "instance methods" but you have to call String->GetLength(myinstance). Polymorphism would be impossible with this tho.

IsA (the ability treat and access an object as if it were some other type) and HasA (the ability to access an object or its properties on properties that match some other type in a consistent and compatible manner).

Using this projects proposal you can create a base interface for types. You can implement a specific implementation for a type and still call methods like in any other "class". Thus it is polymorph by default (it has a superclass)

Just because someone wrote a book 10 years ago which covers the same topic does not mean I should reference them as I would not suggest these books (too old to help with modern programing paradigms except no2). If I used the work of an author specifically (which I did not) citation is needed of course.

Using this projects proposal you can create a base interface for types. You can implement a specific implementation for a type and still call methods like in any other "class". Thus it is polymorph by default (it has a superclass)

I don't know who taught you this technique, but your version is a relatively immature imitation of the basic Object Oriented Principles (Encapsulation, Polymorphism, and Inheritance). The fact is you did not demonstrate traditional nor complete Polymorphism because of the limitations of your design (there is no stable class or prototype on which to polymorph on). This design flaw is fully exhibited in the fact you are not able to create an inheritance chain.

Quote:

Just because someone wrote a book 10 years ago which covers the same topic does not mean I should reference them as I would not suggest these books (too old to help with modern programing paradigms except no2). If I used the work of an author specifically (which I did not) citation is needed of course.

You are sadly mistaken: the links and books I pointed you not only "cover the same topic", they fully explore them. They demonstrate fully working Encapsulation, Polymorphism, and Inheritance in ANSI-C. Such code can be compiled on all the major C compilers and even many (most?) of the embedded C compilers where C is most popular now.

Although it may be thorough to research prior art before being creative, it is not necessary. Sometimes when you have a good idea it is best just to get on and do it. It is vain to expect full OO compliance or to match the convenience of C++ (you are never going to get destructers called as objects go out of scope) but this article presents a relatively non-ugly way of providing some encapsulation.

The difference between MVP and half-baked is sometimes not always clear until you have well crossed over the line[0][1].

Also, it is actually very easy to get out of scope destructors if you're willing to use GCC [2] and if you are careful with how you design your objects. I plan to release a library that can demonstrate such OO capabilities in C shortly.

Hey Member 12225653 it's better than your link to to the OOC project which is about the worst piece of C code macro hell I have ever seen. Somebody really tried way to hard for absolute C++ compatibility and left common sense at the front door. I am not surprised last site update was back in 2012 because no-one would use that rubbish ... I rolled around laughing at his VMT functions.

Your link to embedded.com is essentially the same coding tricks he demonstrated they simply included how to make private data.