160

What would be a set of nifty preprocessor hacks (ANSI C89/ISO C90 compatible) which enable some kind of ugly (but usable) object-orientation in C?

I am familiar with a few different object-oriented languages, so please don't respond with answers like "Learn C++!". I have read "Object-Oriented Programming With ANSI C" (beware: PDF format) and several other interesting solutions, but I'm mostly interested in yours :-)!


See also Can you write object oriented code in C?

Community
  • 1
  • 1
  • 1
    Can I respond to learn D and use the c compatible abi for where you really need C. http://www.digitalmars.com/d/ – Tim Matthews Jan 06 '09 at 11:53
  • Not really. I work with some embedded systems which only really have a C compiler available. –  Jan 06 '09 at 21:51
  • 2
    @Dinah: Thank you for the "See also". That post was interesting. –  Jan 06 '09 at 22:20
  • 1
    The interesting question seems to be why would you want a pre-processor hack of OOP on C. – Calyth Jan 08 '09 at 19:13
  • 3
    @Calyth: I find that OOP is useful and "I work with some embedded systems which only really have a C compiler available" (from above). Moreover, don't you find nifty preprocessor hacks interesting to look at? –  Jan 09 '09 at 21:09
  • 2
    Possible duplicate of [Can you write object-oriented code in C?](http://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c) – Ciro Santilli新疆棉花TRUMP BAN BAD Apr 29 '16 at 09:52

23 Answers23

195

I would advise against preprocessor (ab)use to try and make C syntax more like that of another more object-oriented language. At the most basic level, you just use plain structs as objects and pass them around by pointers:

struct monkey
{
    float age;
    bool is_male;
    int happiness;
};

void monkey_dance(struct monkey *monkey)
{
    /* do a little dance */
}

To get things like inheritance and polymorphism, you have to work a little harder. You can do manual inheritance by having the first member of a structure be an instance of the superclass, and then you can cast around pointers to base and derived classes freely:

struct base
{
    /* base class members */
};

struct derived
{
    struct base super;
    /* derived class members */
};

struct derived d;
struct base *base_ptr = (struct base *)&d;  // upcast
struct derived *derived_ptr = (struct derived *)base_ptr;  // downcast

To get polymorphism (i.e. virtual functions), you use function pointers, and optionally function pointer tables, also known as virtual tables or vtables:

struct base;
struct base_vtable
{
    void (*dance)(struct base *);
    void (*jump)(struct base *, int how_high);
};

struct base
{
    struct base_vtable *vtable;
    /* base members */
};

void base_dance(struct base *b)
{
    b->vtable->dance(b);
}

void base_jump(struct base *b, int how_high)
{
    b->vtable->jump(b, how_high);
}

struct derived1
{
    struct base super;
    /* derived1 members */
};

void derived1_dance(struct derived1 *d)
{
    /* implementation of derived1's dance function */
}

void derived1_jump(struct derived1 *d, int how_high)
{
    /* implementation of derived 1's jump function */
}

/* global vtable for derived1 */
struct base_vtable derived1_vtable =
{
    &derived1_dance, /* you might get a warning here about incompatible pointer types */
    &derived1_jump   /* you can ignore it, or perform a cast to get rid of it */
};

void derived1_init(struct derived1 *d)
{
    d->super.vtable = &derived1_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

struct derived2
{
    struct base super;
    /* derived2 members */
};

void derived2_dance(struct derived2 *d)
{
    /* implementation of derived2's dance function */
}

void derived2_jump(struct derived2 *d, int how_high)
{
    /* implementation of derived2's jump function */
}

struct base_vtable derived2_vtable =
{
   &derived2_dance,
   &derived2_jump
};

void derived2_init(struct derived2 *d)
{
    d->super.vtable = &derived2_vtable;
    /* init base members d->super.foo */
    /* init derived1 members d->foo */
}

int main(void)
{
    /* OK!  We're done with our declarations, now we can finally do some
       polymorphism in C */
    struct derived1 d1;
    derived1_init(&d1);

    struct derived2 d2;
    derived2_init(&d2);

    struct base *b1_ptr = (struct base *)&d1;
    struct base *b2_ptr = (struct base *)&d2;

    base_dance(b1_ptr);  /* calls derived1_dance */
    base_dance(b2_ptr);  /* calls derived2_dance */

    base_jump(b1_ptr, 42);  /* calls derived1_jump */
    base_jump(b2_ptr, 42);  /* calls derived2_jump */

    return 0;
}

And that's how you do polymorphism in C. It ain't pretty, but it does the job. There are some sticky issues involving pointer casts between base and derived classes, which are safe as long as the base class is the first member of the derived class. Multiple inheritance is much harder - in that case, in order to case between base classes other than the first, you need to manually adjust your pointers based on the proper offsets, which is really tricky and error-prone.

Another (tricky) thing you can do is change the dynamic type of an object at runtime! You just reassign it a new vtable pointer. You can even selectively change some of the virtual functions while keeping others, creating new hybrid types. Just be careful to create a new vtable instead of modifying the global vtable, otherwise you'll accidentally affect all objects of a given type.

haccks
  • 97,141
  • 23
  • 153
  • 244
Adam Rosenfield
  • 360,316
  • 93
  • 484
  • 571
  • 6
    Adam, the fun of changing the global vtable of a type is to simulate duck-typing in C. :) – jmucchiello Sep 28 '09 at 14:59
  • Now I pity C++... Well of course the C++ syntax is clearer, but since it's not a trivial syntax, I'm mitigated. I wonder if something hybrid between C++ and C could be achieved, so void* would still be valid castable type. The part with `struct derived {struct base super;};` is obvious to guess how it works, since by the bytes order it's correct. – jokoon Jan 04 '11 at 15:41
  • 2
    +1 for elegant code, well written. This is exactly what I was looking for! – Homunculus Reticulli May 01 '12 at 07:09
  • 3
    Well done. This is exactly how I've been doing it and it is the correct way too. Instead of requiring a pointer to the struct/object in mind you should just pass a pointer to an integer (address). This would allow you to pass in any kind of object for unlimited polymorphic method calls. Also, the only thing missing is a function to initialize your structs (objects/classes). This would include a malloc function and return a pointer. Maybe I'll add a piece of how to do message passing (objective-c) in C. –  Jul 24 '12 at 19:37
  • 1
    This is the straw broke me of C++, and to use C more (before I only used C++ for inheritance) Thank you – Anne Quinn May 25 '13 at 04:05
  • This is how I do oop in c. I believe it's the right way too. –  Jan 08 '15 at 15:54
  • In the inheritance example, why not just `struct base *base_ptr = &d.super; // upcast`? – Joshua Green Feb 13 '18 at 02:17
  • I'm vaguely uncomfortable that some of the nuances here might be undefined behavior. For example, that warning when you set up the vtable is... probably okay to ignore in practice, but I'm pretty sure it's undefined behavior, and we all (should) know how compilers love to interpret undefined behavior as justification for optimizations that break code which relied on the reasonable raw machine behavior underneath the undefined C behavior. I think the well-defined version has the methods take base pointers and cast them to derived pointers, instead of taking derived pointers directly. – mtraceur Feb 06 '21 at 00:24
33

I once worked with a C library that was implemented in a way that struck me as quite elegant. They had written, in C, a way to define objects, then inherit from them so that they were as extensible as a C++ object. The basic idea was this:

  • Each object had its own file
  • Public functions and variables are defined in the .h file for an object
  • Private variables and functions were only located in the .c file
  • To "inherit" a new struct is created with the first member of the struct being the object to inherit from

Inheriting is difficult to describe, but basically it was this:

struct vehicle {
   int power;
   int weight;
}

Then in another file:

struct van {
   struct vehicle base;
   int cubic_size;
}

Then you could have a van created in memory, and being used by code that only knew about vehicles:

struct van my_van;
struct vehicle *something = &my_van;
vehicle_function( something );

It worked beautifully, and the .h files defined exactly what you should be able to do with each object.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Kieveli
  • 10,548
  • 6
  • 48
  • 77
  • I really like this solution, except that all of the "object"'s internals are public. – Lawrence Dol Jan 06 '09 at 05:18
  • 6
    @Software Monkey: C has no access control. The only way to hide implementation details is to interact through opaque pointers, which can get pretty painful, since all fields would need to be accessed through accessor methods which probably can't be inlined. – Adam Rosenfield Jan 06 '09 at 05:26
  • 1
    @Adam: Compilers supporting link-time optimizations will inline them just fine... – Christoph Jan 06 '09 at 13:01
  • 9
    If you do this, you should also ensure that all the functions in the .c file that are not defined as public are defined as static so they don't end up as named functions in your object files. That ensures no one can find their names in the link phase. – jmucchiello Sep 28 '09 at 15:01
  • The downside of this approach is that as the compiler doesn't know 'van' is-a 'vehicle', you'll have to either ignore the compiler warning you'll be given for `struct vehicle *something = &my_van` or add an explicit cast. – Arnout Engelen Apr 09 '11 at 13:21
  • I agree with "each object had it's own file" (header). Grouping multiple 'objects' (especially C ones) can quickly create a huge mess, so a rule of one object per a header is wise. The only work left is a preprocessor trick to standardize the offsets in virtual tables across the various objects that share methods. This is where C++ comes in handy, though doing it C is very crafty indeed. –  Jul 24 '12 at 19:44
  • Or switch to an object oriented language, instead of C? The right tool for the job, guys. Not everything is a nail. – Marcel Valdez Orozco Nov 14 '12 at 23:22
  • 2
    @Marcel: C was used because the code was deployed on low level boards running a variety of processors for autonomous systems. They all supported compiling from C to their respective native binaries. The approach made the code very easy to read once you realized what they were trying to do. – Kieveli Jun 03 '13 at 12:49
  • @AdamRosenfield: it's not entirely correct. you can always provide layout compatible structures to allow data hiding. and casts of course. – Alexander Oh Dec 04 '13 at 16:19
32

C Object System (COS) sounds promising (it's still in alpha version). It tries to keep minimal the available concepts for the sake of simplicity and flexibility: uniform object oriented programming including open classes, metaclasses, property metaclasses, generics, multimethods, delegation, ownership, exceptions, contracts and closures. There is a draft paper (PDF) that describes it.

Exception in C is a C89 implementation of the TRY-CATCH-FINALLY found in other OO languages. It comes with a testsuite and some examples.

Both by Laurent Deniau, which is working a lot on OOP in C.

philant
  • 31,604
  • 11
  • 64
  • 107
  • @vonbrand COS migrated to github where last commit is last summer. Maturity can explain lack of commit. – philant Sep 15 '17 at 18:55
18

The GNOME desktop for Linux is written in object-oriented C, and it has an object model called "GObject" which supports properties, inheritance, polymorphism, as well as some other goodies like references, event handling (called "signals"), runtime typing, private data, etc.

It includes preprocessor hacks to do things like typecasting around in the class hierarchy, etc. Here's an example class I wrote for GNOME (things like gchar are typedefs):

Class Source

Class Header

Inside the GObject structure there's a GType integer which is used as a magic number for GLib's dynamic typing system (you can cast the entire struct to a "GType" to find it's type).

James Cape
  • 684
  • 3
  • 11
7

Slightly off-topic, but the original C++ compiler, Cfront, compiled C++ to C and then to assembler.

Preserved here.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
zebrabox
  • 5,556
  • 25
  • 32
  • I've actually seen it before. I believe it was a nice piece of work. –  Aug 05 '09 at 10:11
  • @Anthony Cuozzo : Stan Lippman wrote a great book called 'C++ - Inside the object model' where he related a lot of his experiences and design decisions in writing and maintaining c-front. It's still a good read and helped me immensely when transitioning from C to C++ many years back – zebrabox Aug 05 '09 at 10:40
6

If you think of methods called on objects as static methods that pass an implicit 'this' into the function it can make thinking OO in C easier.

For example:

String s = "hi";
System.out.println(s.length());

becomes:

string s = "hi";
printf(length(s)); // pass in s, as an implicit this

Or something like that.

jjnguy
  • 128,890
  • 51
  • 289
  • 321
6

I used to do this kind of thing in C, before I knew what OOP was.

Following is an example, which implements a data-buffer which grows on demand, given a minimum size, increment and maximum size. This particular implementation was "element" based, which is to say it was designed to allow a list-like collection of any C type, not just a variable length byte-buffer.

The idea is that the object is instantiated using the xxx_crt() and deleted using xxx_dlt(). Each of the "member" methods takes a specifically typed pointer to operate on.

I implemented a linked list, cyclic buffer, and a number of other things in this manner.

I must confess, I have never given any thought on how to implement inheritance with this approach. I imagine that some blend of that offered by Kieveli might be a good path.

dtb.c:

#include <limits.h>
#include <string.h>
#include <stdlib.h>

static void dtb_xlt(void *dst, const void *src, vint len, const byte *tbl);

DTABUF *dtb_crt(vint minsiz,vint incsiz,vint maxsiz) {
    DTABUF          *dbp;

    if(!minsiz) { return NULL; }
    if(!incsiz)                  { incsiz=minsiz;        }
    if(!maxsiz || maxsiz<minsiz) { maxsiz=minsiz;        }
    if(minsiz+incsiz>maxsiz)     { incsiz=maxsiz-minsiz; }
    if((dbp=(DTABUF*)malloc(sizeof(*dbp))) == NULL) { return NULL; }
    memset(dbp,0,sizeof(*dbp));
    dbp->min=minsiz;
    dbp->inc=incsiz;
    dbp->max=maxsiz;
    dbp->siz=minsiz;
    dbp->cur=0;
    if((dbp->dta=(byte*)malloc((vuns)minsiz)) == NULL) { free(dbp); return NULL; }
    return dbp;
    }

DTABUF *dtb_dlt(DTABUF *dbp) {
    if(dbp) {
        free(dbp->dta);
        free(dbp);
        }
    return NULL;
    }

vint dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(dtalen==-1) { dtalen=(vint)strlen((byte*)dtaptr); }
    if((dbp->cur + dtalen) > dbp->siz) {
        void        *newdta;
        vint        newsiz;

        if((dbp->siz+dbp->inc)>=(dbp->cur+dtalen)) { newsiz=dbp->siz+dbp->inc; }
        else                                       { newsiz=dbp->cur+dtalen;   }
        if(newsiz>dbp->max) { errno=ETRUNC; return -1; }
        if((newdta=realloc(dbp->dta,(vuns)newsiz))==NULL) { return -1; }
        dbp->dta=newdta; dbp->siz=newsiz;
        }
    if(dtalen) {
        if(xlt256) { dtb_xlt(((byte*)dbp->dta+dbp->cur),dtaptr,dtalen,xlt256); }
        else       { memcpy(((byte*)dbp->dta+dbp->cur),dtaptr,(vuns)dtalen);   }
        dbp->cur+=dtalen;
        }
    return 0;
    }

static void dtb_xlt(void *dst,const void *src,vint len,const byte *tbl) {
    byte            *sp,*dp;

    for(sp=(byte*)src,dp=(byte*)dst; len; len--,sp++,dp++) { *dp=tbl[*sp]; }
    }

vint dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...) {
    byte            textÝ501¨;
    va_list         ap;
    vint            len;

    va_start(ap,format); len=sprintf_len(format,ap)-1; va_end(ap);
    if(len<0 || len>=sizeof(text)) { sprintf_safe(text,sizeof(text),"STRTOOLNG: %s",format); len=(int)strlen(text); }
    else                           { va_start(ap,format); vsprintf(text,format,ap); va_end(ap);                     }
    return dtb_adddta(dbp,xlt256,text,len);
    }

vint dtb_rmvdta(DTABUF *dbp,vint len) {
    if(!dbp) { errno=EINVAL; return -1; }
    if(len > dbp->cur) { len=dbp->cur; }
    dbp->cur-=len;
    return 0;
    }

vint dtb_reset(DTABUF *dbp) {
    if(!dbp) { errno=EINVAL; return -1; }
    dbp->cur=0;
    if(dbp->siz > dbp->min) {
        byte *newdta;
        if((newdta=(byte*)realloc(dbp->dta,(vuns)dbp->min))==NULL) {
            free(dbp->dta); dbp->dta=null; dbp->siz=0;
            return -1;
            }
        dbp->dta=newdta; dbp->siz=dbp->min;
        }
    return 0;
    }

void *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen) {
    if(!elmlen || (elmidx*elmlen)>=dbp->cur) { return NULL; }
    return ((byte*)dbp->dta+(elmidx*elmlen));
    }

dtb.h

typedef _Packed struct {
    vint            min;                /* initial size                       */
    vint            inc;                /* increment size                     */
    vint            max;                /* maximum size                       */
    vint            siz;                /* current size                       */
    vint            cur;                /* current data length                */
    void            *dta;               /* data pointer                       */
    } DTABUF;

#define dtb_dtaptr(mDBP)                (mDBP->dta)
#define dtb_dtalen(mDBP)                (mDBP->cur)

DTABUF              *dtb_crt(vint minsiz,vint incsiz,vint maxsiz);
DTABUF              *dtb_dlt(DTABUF *dbp);
vint                dtb_adddta(DTABUF *dbp,const byte *xlt256,const void *dtaptr,vint dtalen);
vint                dtb_addtxt(DTABUF *dbp,const byte *xlt256,const byte *format,...);
vint                dtb_rmvdta(DTABUF *dbp,vint len);
vint                dtb_reset(DTABUF *dbp);
void                *dtb_elmptr(DTABUF *dbp,vint elmidx,vint elmlen);

PS: vint was simply a typedef of int - I used it to remind me that it's length was variable from platform to platform (for porting).

Lawrence Dol
  • 59,198
  • 25
  • 134
  • 183
  • 7
    holy moly, this could win an obfuscated C contest! i like it! :) – horseyguy Jul 29 '09 at 19:58
  • @horseyguy No it couldn't. It's been published. Also they consider inclusion of header files abuse against the iocccsize tool. It's also not a complete program. 2009 had no contest so can't compare the iocccsize. The CPP has been abused many many times also so it's fairly old. Etc. Sorry. I'm not trying to be negative I am however realistic. I sort of get your meaning though and it's a good read and I have up-voted it. (And yes I participate in it and yes I win too.) – Pryftan Feb 24 '20 at 16:46
4

I think what Adam Rosenfield posted is the correct way of doing OOP in C. I'd like to add that what he shows is the implementation of the object. In other words the actual implementation would be put in the .c file, while the interface would be put in the header .h file. For example, using the monkey example above:

The interface would look like:

//monkey.h

    struct _monkey;

    typedef struct _monkey monkey;

    //memory management
    monkey * monkey_new();
    int monkey_delete(monkey *thisobj);
    //methods
    void monkey_dance(monkey *thisobj);

You can see in the interface .h file you are only defining prototypes. You can then compile the implementation part " .c file" into a static or dynamic library. This creates encapsulation and also you can change the implementation at will. The user of your object needs to know almost nothing about the implementation of it. This also places focus on the overall design of the object.

It's my personal belief that oop is a way of conceptualizing your code structure and reusability and has really nothing to do with those other things that are added to c++ like overloading or templates. Yes those are very nice useful features but they are not representative of what object oriented programming really is.

  • You can declare a struct with `typedef struct Monkey {} Monkey;` What's the point of typedef'ing it after it's been created? – MarcusJ Nov 20 '16 at 03:50
  • 1
    @MarcusJ The `struct _monkey` is simply a prototype. The actual type definition is defined in the implementation file (the .c file ). This creates the encapsulation effect and allows the API developer to redefine the monkey structure in the future without modifying the API. Users of the API only need to be concerned with the actual methods. The API designer takes care of the implementation including how the object/struct is laid out. So the object/struct's details are hidden from the user (an opaque type). –  Nov 21 '16 at 20:25
  • I define my structs in the headers, is this not standard? Well, I do it that way because I occasionally need to access members of the struct outside of that library. – MarcusJ Nov 22 '16 at 21:11
  • 1
    @MarcusJ You can define you structs in the headers if you wish (there is no standard). But if you want to change it's internal structure down the road you may break your code. Encapsulation is merely a style of coding that makes it easier to change an implementation without breaking your code. You can always access your members through accessor methods like `int getCount(ObjectType obj)` etc if you choose to define the struct in the implementation file. –  Nov 23 '16 at 21:02
4

ffmpeg (a toolkit for video processing) is written in straight C (and assembly language), but using an object-oriented style. It's full of structs with function pointers. There are a set of factory functions that initialize the structs with the appropriate "method" pointers.

Mr Fooz
  • 95,588
  • 5
  • 65
  • 95
  • i don't see any factory functions in it(ffmpeg), rather it doesnt seem to be using polymorphism/inheritance ( trivial way suggested above). – FL4SOF Jan 06 '09 at 05:39
  • avcodec_open is one factory function. It stuffs function pointers into a AVCodecContext struct (like draw_horiz_band). If you look at FF_COMMON_FRAME macro usage in avcodec.h, you'll see something akin to inheritance of data members. IMHO, ffmpeg proves to me that OOP is best done in C++, not C. – Mr Fooz Jan 06 '09 at 23:41
3

If you really thinks catefully, even standard C library use OOP - consider FILE * as an example: fopen() initializes an FILE * object, and you use it use member methods fscanf(), fprintf(), fread(), fwrite() and others, and eventually finalize it with fclose().

You can also go with the pseudo-Objective-C way which is not difficult as well:

typedef void *Class;

typedef struct __class_Foo
{
    Class isa;
    int ivar;
} Foo;

typedef struct __meta_Foo
{
    Foo *(*alloc)(void);
    Foo *(*init)(Foo *self);
    int (*ivar)(Foo *self);
    void (*setIvar)(Foo *self);
} meta_Foo;

meta_Foo *class_Foo;

void __meta_Foo_init(void) __attribute__((constructor));
void __meta_Foo_init(void)
{
    class_Foo = malloc(sizeof(meta_Foo));
    if (class_Foo)
    {
        class_Foo = {__imp_Foo_alloc, __imp_Foo_init, __imp_Foo_ivar, __imp_Foo_setIvar};
    }
}

Foo *__imp_Foo_alloc(void)
{
    Foo *foo = malloc(sizeof(Foo));
    if (foo)
    {
        memset(foo, 0, sizeof(Foo));
        foo->isa = class_Foo;
    }
    return foo;
}

Foo *__imp_Foo_init(Foo *self)
{
    if (self)
    {
        self->ivar = 42;
    }
    return self;
}
// ...

To use:

int main(void)
{
    Foo *foo = (class_Foo->init)((class_Foo->alloc)());
    printf("%d\n", (foo->isa->ivar)(foo)); // 42
    foo->isa->setIvar(foo, 60);
    printf("%d\n", (foo->isa->ivar)(foo)); // 60
    free(foo);
}

This is what may be resulted from some Objective-C code like this, if a pretty-old Objective-C-to-C translator is used:

@interface Foo : NSObject
{
    int ivar;
}
- (int)ivar;
- (void)setIvar:(int)ivar;
@end

@implementation Foo
- (id)init
{
    if (self = [super init])
    {
        ivar = 42;
    }
    return self;
}
@end

int main(void)
{
    Foo *foo = [[Foo alloc] init];
    printf("%d\n", [foo ivar]);
    [foo setIvar:60];
    printf("%d\n", [foo ivar]);
    [foo release];
}
Maxthon Chan
  • 1,165
  • 8
  • 14
2

My recommendation: keep it simple. One of the biggest issues I have is maintaining older software (sometimes over 10 years old). If the code is not simple, it can be difficult. Yes, one can write very useful OOP with polymorphism in C, but it can be difficult to read.

I prefer simple objects that encapsulate some well-defined functionality. A great example of this is GLIB2, for example a hash table:

GHastTable* my_hash = g_hash_table_new(g_str_hash, g_str_equal);
int size = g_hash_table_size(my_hash);
...

g_hash_table_remove(my_hash, some_key);

The keys are:

  1. Simple architecture and design pattern
  2. Achieves basic OOP encapsulation.
  3. Easy to implement, read, understand, and maintain
Wadester
  • 343
  • 2
  • 5
1
#include "triangle.h"
#include "rectangle.h"
#include "polygon.h"

#include <stdio.h>

int main()
{
    Triangle tr1= CTriangle->new();
    Rectangle rc1= CRectangle->new();

    tr1->width= rc1->width= 3.2;
    tr1->height= rc1->height= 4.1;

    CPolygon->printArea((Polygon)tr1);

    printf("\n");

    CPolygon->printArea((Polygon)rc1);
}

Output:

6.56
13.12

Here is a show of what is OO programming with C.

This is real, pure C, no preprocessor macros. We have inheritance, polymorphism and data encapsulation (including data private to classes or objects). There is no chance for protected qualifier equivalent, that is, private data is private down the innheritance chain too. But this is not an inconvenience because I don't think it is necessary.

CPolygon is not instantiated because we only use it to manipulate objects of down the innheritance chain that have common aspects but different implementation of them (Polymorphism).

nhahtdh
  • 52,949
  • 15
  • 113
  • 149
rogergc
  • 29
  • 1
  • I think this answer would get a lot more +1 votes if it had an example of the implementations, rather than just an example of the usages. I can imagine an implementation, but only because I've already thought about it a lot and learned from other object-oriented C implementations. Someone who is still asking the above question won't have nearly as easy of a time figuring out how to do it. – mtraceur Feb 06 '21 at 03:22
  • That said, +1 from me, because this is in some subtle ways a different variation than all of the above answers, which at a glance seems to have some possible advantages over the other answers. – mtraceur Feb 06 '21 at 03:24
1

If I were going to write OOP in C I would probably go with a pseudo-Pimpl design. Instead of passing pointers to structs, you end up passing pointers to pointers to structs. This makes the content opaque and facilitates polymorphism and inheritance.

The real problem with OOP in C is what happens when variables exit scope. There are no compiler-generated destructors and that can cause issues. Macros can possibly help, but it is always going to be ugly to look at.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
jmucchiello
  • 17,551
  • 5
  • 37
  • 59
  • 1
    When programming in C, I deal with scope by using `if` statements and releasing them at the end. For example `if ( (obj = new_myObject()) ) { /* code using myObject */ free_myObject(obj); }` –  Nov 23 '16 at 21:08
1

I'm also working on this based on a macro solution. So it is for the bravest only, I guess ;-) But it is quite nice already, and I'm already working on a few projects on top of it. It works so that you first define a separate header file for each class. Like this:

#define CLASS Point
#define BUILD_JSON

#define Point__define                            \
    METHOD(Point,public,int,move_up,(int steps)) \
    METHOD(Point,public,void,draw)               \
                                                 \
    VAR(read,int,x,JSON(json_int))               \
    VAR(read,int,y,JSON(json_int))               \

To implement the class, you create a header file for it and a C file where you implement the methods:

METHOD(Point,public,void,draw)
{
    printf("point at %d,%d\n", self->x, self->y);
}

In the header you created for the class, you include other headers you need and define types etc. related to the class. In both the class header and in the C file you include the class specification file (see the first code example) and an X-macro. These X-macros (1,2,3 etc.) will expand the code to the actual class structs and other declarations.

To inherit a class, #define SUPER supername and add supername__define \ as the first line in the class definition. Both must be there. There is also JSON support, signals, abstract classes, etc.

To create an object, just use W_NEW(classname, .x=1, .y=2,...). The initialization is based on struct initialization introduced in C11. It works nicely and everything not listed is set to zero.

To call a method, use W_CALL(o,method)(1,2,3). It looks like a higher order function call but it is just a macro. It expands to ((o)->klass->method(o,1,2,3)) which is a really nice hack.

See Documentation and the code itself.

Since the framework needs some boilerplate code, I wrote a Perl script (wobject) that does the job. If you use that, you can just write

class Point
    public int move_up(int steps)
    public void draw()
    read int x
    read int y

and it will create the class specification file, class header, and a C file, which includes Point_impl.c where you implement the class. It saves quite a lot of work, if you have many simple classes but still everything is in C. wobject is a very simple regular expression based scanner which is easy to adapt to specific needs, or to be rewritten from scratch.

J.P.
  • 9
  • 3
  • +1 for being a somewhat different approach to all the others above. In some ways it's nice in ways that other macro-based solutions mentioned above aren't. – mtraceur Feb 06 '21 at 03:27
1

Another way to program in an object oriented style with C is to use a code generator which transforms a domain specific language to C. As it's done with TypeScript and JavaScript to bring OOP to js.

1

You can try out COOP, a programmer-friendly framework for OOP in C, features Classes, Exceptions, Polymorphism, and Memory Management (important for Embedded code). It's a relatively lightweight syntax, see the tutorial in the Wiki there.

Shmuel Fine
  • 341
  • 1
  • 4
0

@Adam Rosenfield has a very good explanation of how to achieve OOP with C

Besides, I would recommend you to read

1) pjsip

A very good C library for VoIP. You can learn how it achieves OOP though structs and function pointer tables

2) iOS Runtime

Learn how iOS Runtime powers Objective C. It achieves OOP through isa pointer, meta class

onmyway133
  • 38,911
  • 23
  • 231
  • 237
0

Look at http://ldeniau.web.cern.ch/ldeniau/html/oopc/oopc.html. If nothing else reading through the documentation is an enlightening experience.

Tim
  • 1
  • 3
    Please provide context for the link you are sharing. Although the link you shared may indeed be very helpful, it is advisable to rather capture the key aspects of the shared article that respond to the question. This way, even if the link is removed your answer will still be relevant and helpful. – ishmaelMakitla Aug 08 '16 at 12:45
0

For me object orientation in C should have these features:

  1. Encapsulation and data hiding (can be achieved using structs/opaque pointers)

  2. Inheritance and support for polymorphism (single inheritance can be achieved using structs - make sure the abstract base is not instantiable)

  3. Constructor and destructor functionality (not easy to achieve)

  4. Type checking (at least for user-defined types as C doesn't enforce any)

  5. Reference counting (or something to implement RAII)

  6. Limited support for exception handling (setjmp and longjmp)

On top of the above it should rely on ANSI/ISO specifications and should not rely on compiler-specific functionality.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
FL4SOF
  • 3,661
  • 6
  • 24
  • 24
  • For number (5) - You can't implement RAII in a language without destructors (which means RAII is not a compiler-supported technique in C or Java). – Tom Jan 06 '09 at 06:05
  • constructors and destructors can be written for c based object - i guess GObject does it. and ofcourse RAAI ( it is not straight forward, may be ugly and need not be pragmatic at all) - all i was looking is to identify C based semantics to acheive the above. – FL4SOF Jan 06 '09 at 06:44
  • C doesn't support destructors. You have to type *something* in order to make them work. That means they don't clean up themselves. GObject doesn't change the language. – Tom Jan 07 '09 at 05:25
0

I'm a bit late to the party here but I like to avoid both macro extremes - too many or too much obfuscates code, but a couple obvious macros can make the OOP code easier to develop and read:

/*
 * OOP in C
 *
 * gcc -o oop oop.c
 */

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

struct obj2d {
    float x;                            // object center x
    float y;                            // object center y
    float (* area)(void *);
};

#define X(obj)          (obj)->b1.x
#define Y(obj)          (obj)->b1.y
#define AREA(obj)       (obj)->b1.area(obj)

void *
_new_obj2d(int size, void * areafn)
{
    struct obj2d * x = calloc(1, size);
    x->area = areafn;
    // obj2d constructor code ...
    return x;
}

// --------------------------------------------------------

struct rectangle {
    struct obj2d b1;        // base class
    float width;
    float height;
    float rotation;
};

#define WIDTH(obj)      (obj)->width
#define HEIGHT(obj)     (obj)->height

float rectangle_area(struct rectangle * self)
{
    return self->width * self->height;
}

#define NEW_rectangle()  _new_obj2d(sizeof(struct rectangle), rectangle_area)

// --------------------------------------------------------

struct triangle {
    struct obj2d b1;
    // deliberately unfinished to test error messages
};

#define NEW_triangle()  _new_obj2d(sizeof(struct triangle), triangle_area)

// --------------------------------------------------------

struct circle {
    struct obj2d b1;
    float radius;
};

#define RADIUS(obj)     (obj)->radius

float circle_area(struct circle * self)
{
    return M_PI * self->radius * self->radius;
}

#define NEW_circle()     _new_obj2d(sizeof(struct circle), circle_area)

// --------------------------------------------------------

#define NEW(objname)            (struct objname *) NEW_##objname()


int
main(int ac, char * av[])
{
    struct rectangle * obj1 = NEW(rectangle);
    struct circle    * obj2 = NEW(circle);

    X(obj1) = 1;
    Y(obj1) = 1;

    // your decision as to which of these is clearer, but note above that
    // macros also hide the fact that a member is in the base class

    WIDTH(obj1)  = 2;
    obj1->height = 3;

    printf("obj1 position (%f,%f) area %f\n", X(obj1), Y(obj1), AREA(obj1));

    X(obj2) = 10;
    Y(obj2) = 10;
    RADIUS(obj2) = 1.5;
    printf("obj2 position (%f,%f) area %f\n", X(obj2), Y(obj2), AREA(obj2));

    // WIDTH(obj2)  = 2;                                // error: struct circle has no member named width
    // struct triangle  * obj3 = NEW(triangle);         // error: triangle_area undefined
}

I think this has a good balance, and the errors it generates (at least with default gcc 6.3 options) for some of the more likely mistakes are helpful instead of confusing. The whole point is to improve programmer productivity no?

duanev
  • 834
  • 12
  • 14
0

If you need to write a little code try this: https://github.com/fulminati/class-framework

#include "class-framework.h"

CLASS (People) {
    int age;
};

int main()
{
    People *p = NEW (People);

    p->age = 10;

    printf("%d\n", p->age);
}
cicciodarkast
  • 329
  • 2
  • 10
  • 2
    Please don't just post some tool or library as an answer. At least demonstrate [how it solves the problem](http://meta.stackoverflow.com/a/251605) in the answer itself. – Baum mit Augen Aug 25 '17 at 23:59
0

The open-source Dynace project does exactly that. It's at https://github.com/blakemcbride/Dynace

Blake McBride
  • 313
  • 1
  • 12
-2

I have a small pet project on this topic. It supports encapsulation, single inheritance, and polymorphism. If someone is still interested here is the link to the repo: https://github.com/alexmarincu/Cbject

And here is an example of the syntax:

Shape.h:

#ifndef SHAPE_H
#define SHAPE_H
#include "../Cbj/Cbj.h" // includes Cbject (base object) and macros
#include "Point.h"

#define Type Shape
#define Parent Cbject

AbstractClass(
    Params(_, Point origin),
    Props(_, Point origin),
    VirtFuns(_,
        (float, area, (0)),
        (void, draw, (_, uint8 const a))));

Setters(_, (Point, origin));
Getters(_, (Point, origin));
SuperFun(void, draw, (_, uint8 const a));

#undef Parent
#undef Type
#endif // SHAPE_H

Shape.c:

#include "Shape.h"

#define Type Shape
#define Parent Cbject

AbstractClassSetup(
    VirtFunCalls(_,
        (float, area, (0), (0)),
        (void, draw, (_, uint8 const a), (_, a))),
    BindFuns(_,
        (void, Shape, draw, (_, uint8 const a))));

Init { me->p.origin = params->origin; }
Terminate {}

DefaultSet(Point, origin);
DefaultGet(Point, origin);

SuperFun(void, draw, (_, uint8 const a))
{
    // Default implementation of draw function
}

#undef Parent
#undef Type

Circle.h:

#ifndef CIRCLE_H
#define CIRCLE_H
#include "Shape.h"

#define Type Circle
#define Parent Shape

Class(
    Params(_,
        Point origin,
        uint32 radius),
    Props(_, int32 radius),
    VirtFuns(0));

Set(uint32, radius);
Get(uint32, radius);

SuperFuns(_,
    (float, area, (0)),
    (void, draw, (_, uint8 const a)));

#undef Type
#undef Parent
#endif // CIRCLE_H

Circle.c:

#include "Circle.h"

#define Type Circle
#define Parent Shape

ClassSetup(
    VirtFunCalls(_, (void, rotate, (0), (0))),
    BindFuns(_,
        (float, Shape, area, (0)),
        (void, Shape, draw, (_, uint8 const a))));

PrivateConst(float, pi = 3.14);

Init
{
    *s_params = (ShapeParams){
        .origin.x = params->origin.x,
        .origin.y = params->origin.y};

    me->p.radius = params->radius;
}

Terminate {}

DefaultSet(uint32, radius);
DefaultGet(uint32, radius);

SuperFun(void, draw, (_, uint8 const a))
{
    s_Shape_draw((Shape *) me, a); // Calling overridden function from parent
    // Specific implementation of draw function
}

SuperFun(float, area, (0)) { return me->p.radius * me->p.radius * Circle_pi; } // Specific implementation of area function

#undef Parent
#undef Type

Usage:

...
{
    Circle * circle = New_Circle(&((CircleParams){.origin.x = 0, .origin.y = 1, .radius = 1})); // create circle
    uint32 radius = Circle_radius(circle)); // get radius
    Circle_radiusSet(circle, 2); // set radius
    float area = Shape_area(Circle_toShape(circle))); // polymorphic call
    Delete_Circle(circle);
}

No documentation available yet, but the examples should be fairly easy to understand.

Alex
  • 11
  • 3