Object-Oriented Programming in C

C is not, of course, an object oriented language and does not even have any discernible features of one. The three core characteristics of object oriented programming are frequently stated to be encapsulation, inheritance and polymorphism, but a more fundamental characteristic is the combining of data (or properties) and the functions (or methods) which work on that data into a single entity which can be used to access both. This can be simulated in C by adding function pointers to a struct. The nuts and bolts of doing so are not as slick as in a real OOP language such as C++ and frankly look a bit clunky, but in this post I will write a short demonstration of the principle. Whether it is actually worthwhile is a moot point!

In this project I will write a very simple "class" called Photo - it will have two "properties", Title and Description, and one "method", print. This is the bare minimum to demonstrate the technique, but if you should decide to take it further all you need to do is add more properties and methods.

Create a new folder and within it create the following empty files. You can download the source code from the Downloads page or clone/download from Github if you prefer.

  • photo.h
  • photo.c
  • main.c

Open photo.h and enter the following.

photo.h

//----------------------------------------------------------------
// STRUCT Photo
//----------------------------------------------------------------
typedef struct Photo
{
    char* Title;
    char* Description;
    void (*print)(struct Photo*);
}Photo;

//----------------------------------------------------------------
// FUNCTION PROTOTYPES
//----------------------------------------------------------------
void new_photo(Photo* photo, char* Title, char* Description);
void print_photo(struct Photo* photo);

Here we just declare a struct and two function prototypes. Now open photo.c and enter this code.

photo.c

#include<stdlib.h>
#include<stdio.h>

#include"photo.h"

//----------------------------------------------------------------
// FUNCTION print_photo
//----------------------------------------------------------------
void print_photo(struct Photo* photo)
{
    printf("Title:       %s\n", photo->Title);
    printf("Description: %s\n", photo->Description);
}

//----------------------------------------------------------------
// FUNCTION NewPhoto
//----------------------------------------------------------------
void new_photo(Photo* photo, char* Title, char* Description)
{
    // Properties
    photo->Title = Title;
    photo->Description = Description;

    // Methods
    photo->print = print_photo;
}

In photo.c, after the #includes, we implement print_photo and new_photo.

Note that print_photo has a Photo* argument so it knows which Photo to print, even though a pointer to the function is later added to a struct. Object oriented languages have some concept of self-referencing, for example C++, C# and other curly-bracket OOP languages have the keyword this, Visual Basic has the keyword me and Python has the keyword self. These are used by class methods when they need to use other members of their own class. C does not have anything like this so even if a pointer to a function is added to a struct the function still needs to be told (via an argument) the actual struct it is to work with.

OOP languages also have syntax to create a new instance of a class, for example using the new keyword which calls a special method in the class called the constructor. Again C does not have this so we need a function, in this case new_photo, which can be called by name rather than a fixed keyword. Our new_photo function takes a Photo struct declared by the calling code as well as values for the two properties, and then sets the property values from the arguments.

After this it does something which is core to the whole concept of simulating OOP in C - it sets the print property to print_photo, or to be precise to a function pointer.

To summarise the process of object oriented programming in C:

  • Add a suitable function pointer to your struct for each function you need to work with that struct. It must have at least one argument - a pointer to the struct.

  • Prototype and implement the functions, using the struct pointer to access the struct.

  • Create a single "constructor" function along the lines of new_photo to set properties and also function pointers.

That was probably simpler than you might have thought, so let's go ahead and use our new Photo class. Open main.c and enter this code.

main.c

#include<stdio.h>
#include<math.h>
#include<time.h>
#include<locale.h>
#include<stdlib.h>

#include"photo.h"

//--------------------------------------------------------
// FUNCTION main
//--------------------------------------------------------
int main(int argc, char* argv[])
{
    puts("----------------------------------------------------\n| code-in-c.com - Object Oriented Programming in C |\n----------------------------------------------------\n");

    Photo p1;
    new_photo(&p1, "Thing", "Photograph of a thing");
    p1.print(&p1);

    Photo p2;
    new_photo(&p2, "Another Thing", "Photograph of another thing");
    p2.print(&p2);

    Photo p3;
    new_photo(&p3, "Yet Another Thing", "Photograph of yet another thing");
    p3.print(&p3);
    
    return EXIT_SUCCESS;
}

The main function does the same thing three times as you can see, so let's just dissect one of the usages.

1 Photo p1;
2 new_photo(&p1, "Thing", "Photograph of a thing");
3 p1.print(&p1);

Line 1 just creates a new variable of type Photo.

Line 2 is the equivalent of a constructor call in a proper OOP language, although in such a language lines 1 and 2 would probably be combined.

Line 3 calls the print method in a way which looks very object oriented in that we first have the object name followed by the method name using dot notation. The only giveaway that we are not actually using an OOP language is the pointer to the struct as a function argument.

You can now compile and run the program with this in your terminal.

Compile and run

gcc main.c photo.c -std=c11 -lm -o main
./main

Program output

----------------------------------------------------
| code-in-c.com - Object Oriented Programming in C |
----------------------------------------------------

Title:       Thing
Description: Photograph of a thing
Title:       Another Thing
Description: Photograph of another thing
Title:       Yet Another Thing
Description: Photograph of yet another thing

So was it worth it? We could have achieved the same result without bothering to add a function pointer to the struct using code like this. (Incidentally this code will work fine with the existing photo.h and photo.c. It just bypasses the new_photo function and print_photo pointer.)

1 Photo p1;
2 p1.Title ="Thing";
3 p1.Description = "Photograph of a thing";
4 print_photo(&p1);

For a simple example like this, the answer is probably no, it isn't worthwhile. However, few real world examples are this simple and if you have a more complex program with a number of more complex structs it may make the code more readable if you explicitly tie together the structs and the code which works on them. In such situations you might well decide it is worth stepping up to C++ but that is a big step. C++ is far more complex than C, and even more complex that its original name of "C with Classes" might imply. I worked in C++ for a number of years and frankly I don't like it. Maybe Java and C# owe their success to many other people not liking C++ and looking for a better alternative! But if you want to stick with C but think a little bit of OOP might come in handy occasionally give the ideas in this post a try.

Please let me have your comments and suggestions, and follow Code in C on Twitter for news of future posts.

3 thoughts on “Object-Oriented Programming in C

  1. > Object oriented languages have some concept of self-referencing, for example C++, C# and other curly-bracket OOP languages have the keyword this, Visual Basic has the keyword me and Python has the keyword self.

    There’s a neat pointer trick used throughout the Linux kernel for finding the address of a structure given one of its members. A good example of its use is in include/linux/list.h.

    The trick is enshrined in a pair of macros: container_of(ptr, type, member), which is in include/linux/kernel.h, which makes use of the second macro offsetof(TYPE, MEMBER), which is in include/linux/stddef.h

    #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

    #define container_of(ptr, type, member) ({ \
    const typeof( ((type *)0)->member ) *__mptr = (ptr); \
    (type *)( (char *)__mptr – offsetof(type,member) );})

    An dumb example is below. We’re passing container_of the address of the function, and it returns the address of ‘test’. From there we can access the function, pass itself as an argument, and get the correct value for test.ret. Of course, it takes a few less characters of to just type “this” as implemented in other OOP languages.

    struct tmp_struct {
    int ret;
    int (*fn)(struct tmp_struct*);
    };

    int tmp_struct_fn(struct tmp_struct *tmp) {
    return tmp->ret;
    }

    void test_tmp_struct(void) {
    int ret;
    struct tmp_struct test, *cont;
    test.ret = 1;
    test.fn = tmp_struct_fn;
    cont = container_of(&test.fn, struct tmp_struct, fn);
    ret = cont->fn(cont);
    printf(“%s, ret = %d\n”, __func__, ret);
    }

    • Thanks for your comment – that’s very interesting. I’ll give it a try. I wonder if there is a Windows equivalent or maybe even a cross-platform method. Probably being way too optimistic.

      Sometime I might write more about OOP in C if I can get enough extra material together. I’m quite keen on getting some benefits of OOP without the rather messy design and excess baggage of C++.

      • The macros are valid C code, (i.e., no compiler-dependent extensions required), so it should run happily on any platform C can be compiled.

Leave a Reply

Your email address will not be published. Required fields are marked *