Skip to content

Instantly share code, notes, and snippets.

@InNoHurryToCode
Last active May 1, 2020 05:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save InNoHurryToCode/d7a419a649b9d8081ba09f70d87ec449 to your computer and use it in GitHub Desktop.
Save InNoHurryToCode/d7a419a649b9d8081ba09f70d87ec449 to your computer and use it in GitHub Desktop.
Some notes on object oriented programming in c

Notes on Object Oriented Programming in C

In this document, I'll discuss a few techniques that can be utilized to achieve object-oriented programming in C. This document is for educational use, and should be evaluated properly before using these techniques in commercial projects.

Note of the author

There isn't much use for object-oriented programming in C besides when one or multiple of these are true:

  1. you want to learn how object-oriented programming languages work
  2. your compiler doesn't support an object-oriented language
  3. object-oriented languages are not capable of utilizing the resources at hand efficiently enough
  4. your boss and/or client demands you to write object-oriented code
  5. alternatives are too complex to learn
  6. you prefer not to use object-oriented languages

Whenever that not the case, prefer to use an object-oriented language.

Inheritance

Inheritance is one of the most essensial parts of object-oriented programming, as it defines the relation between objects. There are multiple ways of implementing inheritance in C, and inheritance by composition is the most widely used implementation. It is not recommended to utilize any kind of inheritance in preformance-critical parts of your code.

The principle of inheritance by composition that objects achieve polymorphic behavior and code reuse by their composition, by containing instances of the base objects that implement the desired functionality. This is how inheritance is done in C++, C#, Java and most other object-oriented languages.

Let's take this C# code for example:

/* source */
public class foo {
    public int x = 0;
    
    public foo() {
        x = 1;
    }
}

public class bar : foo {
    public int y = 0;

    public bar() : base() {
        y = base.x;
    }
}

For C++ it would look like this:

/* header */
class foo {
    public:
        int x;
        foo();
};

class bar : public foo {
    public:
        int y;
        bar();
};

/* source */
foo::foo(void) {
    x = 1;
}

bar::bar(void) : foo() {
    y = foo::x;
}

And this is how it can be implemented in C:

/* header */
typedef struct foo {
    int x;
} foo;

typedef struct bar {
    foo base;       /* bar derived from foo */
    int y;
} bar;

void foo_constructor(foo *const p);
void bar_constructor(bar *const p);

/* source */
void foo_constructor(foo *p) {
    p->x = 1;
}

void bar_constructor(bar *p) {
    foo_constructor(&p->base);
    
    p->y = p->base.x;
}

In both examples, the constructor of foo is called, in which x is set to 1. Then the constructor of bar is called, and y is set to the value of x.

The big differences between the code from C# & C++ and C is that C# and C++ uses classes, and contains the constructor within the class rather than seperate functions. Besides that, the constructor of base foo in the C example has to be called manually.

The reason why the C example works well is that the first member embedded in the derived structure is the base structure. This also means that you can always safely pass a pointer to the derived structure to any function that expects a pointer to the base structure. But why does it work? The C standard reads the following:

There may be unnamed padding within a structure object, but not at its beginning.

For example:

/* source */
int main(void) {
    bar derived;
    
    foo_constructor((foo *)derived);
    
    return 0;
}

There is just a very slight memory overhead (about 4 bytes) in the derived structures to contain the base structure, and the constructor and destructor functions have a slight overhead as they have to be called manually.

However, this technique is relative easy to understand and portable (supported in C 89 and newer, and all versions of C++).

Data hiding

Commonly used with and without object-oriented programming. It allows to hide members from other programmers, to avoid others from accessing the hidden data for various reasons. There is only one technique I know of in C to achieve this, which is discussed below.

Let's take this C# code for example:

/* source */
public class bar {
    public int y { get; private set; }

    public bar() : base() {
        y = base.x;
    }
}

For C++ it would look like this:

/* header */
class bar {        
    public:
        int &get_y() const { return y; }
        bar();
    private:
        int y;
};

/* source */
bar::bar(void) {
    y = 1;
}

And this is how it can be implemented in C:

/* header */
typedef struct bar {
    void *private;  /* private data members */
} bar;

void bar_constructor(bar *const p);
void bar_destructor(bar *const p);
int get_bar_y(bar *cont p);

/* source */
typedef struct private_bar {
    int y;          /* private data members */
} private_bar;

void bar_constructor(bar *const p) {
    p->private = malloc(sizeof(struct private_bar));
    ((private_bar *)p->private)->y = 1;
}

void bar_destructor(bar *const p) {
    free(p->private);
}

int get_bar_y(bar *const p) {
    return ((private_bar *)p->private)->y;
}

The variable y is now a private data member that cannot be changed outside of the class (C#, C++) or source file (C). You can still get the value of y by using bar.y (C#), bar::get_y() (C++), or get_bar_y() (C).

The C example proved that we were able to do something similiar as the C# and C++ examples. The trick is that the void* can't be casted to the private structure because the private structure type is unknown outside of the source file, thus there is no way it can be accessed outside of the source file.

The problem of this technique is that it's not really private, protected or like any other access modifier. Everything inside the private structure simply not known to the world outside of the source file. So yes, it is still possible to access the private structure inside of the source file.

While the obvious upside of the technique is that we can hide the data outside of the source file, the downside is the overhead in the constructor and destructor, as well as the risk of memory fragmentation (unless a memory pool is implemented, which isn't in the example for sake of simplicity).

Virtual functions

If we combine the inheritance by composition and data hiding together, we almost have a full implementation of a class. The only thing missing are methods. This can be emulated with function pointers.

For example:

/* header */
typedef bar bar;

typedef struct foo {
    int x;
} foo;

struct bar {
    foo base;                   /* bar derived from foo */
    void *private;              /* private data members */
    int (*get_y)(bar *const);   /* function pointer as method */
}

However, while we now have added something to act as a method, in C# and C++ we can also use overridable methods called virtual methods. The advantage of virtual methods is to change the behavior of a(n) (implemented) method. We will use a Virtual Method Table (VMT) or better known as a VTable to implement them in C.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment