170

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
Machavity
  • 30,841
  • 27
  • 92
  • 100
Ben Gartner
  • 14,413
  • 10
  • 36
  • 34
  • 34
    Obligatory question: Do you have to write object-oriented code? If you do for whatever reason, that's fine, but you will be fighting a rather uphill battle. It's probably for the best if you avoid trying to write object-oriented code in C. It's certainly possible - see unwind's excellent answer - but it's not exactly "easy," and if you're working on an embedded system with limited memory, it may not be feasible. I may be wrong, though - I'm not trying to argue you out of it, just present some counterpoints that may not have been presented. – Chris Lutz Sep 10 '09 at 08:04
  • 1
    Strictly speaking, we don't have to. However, the complexity of the system has made the code unmaintainable. My feeling is that the best way to reduce the complexity is to implement some OOP concepts. Thanks for everyone who responding within 3 minutes. You guys are insane and quick! – Ben Gartner Sep 10 '09 at 08:09
  • 10
    This is just my humble opinion, but OOP doesn't make code instantly maintainable. It may make it easier to manage, but not necessarily more maintainable. You can have "namespaces" in C (the Apache Portable Runtime prefixes all globals symbols with `apr_` and GLib prefixes them with `g_` to create a namespace) and other organizing factors without OOP. If you're going to be restructuring the app anyway, I would consider spending some time trying to come up with a more maintainable procedural structure. – Chris Lutz Sep 10 '09 at 08:14
  • this has been discussed endlessly before -- did you look at any of the previous answers? – Larry Watanabe Sep 12 '09 at 23:09
  • A lot of these comments attempt to *not* answer the question lol. Why all the discouragement? Im interested in this question as well - just from a purely academic manner. – bbqchickenrobot Mar 15 '21 at 01:24
  • 1
    @RubenSteins book reference is no longer valid. – MikeF Dec 02 '22 at 18:35
  • This source, which was in a deleted answer and outdated comment of mine may also be of help: https://www.cs.rit.edu/~ats/books/ooc.pdf. It describes a complete approach for doing OO in C. – Ruben Steins Dec 05 '22 at 14:43

16 Answers16

108

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 * rect->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_lengths(&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.

Also note that there's no explicit "class" being modelled above, each object has its own method pointers which is a bit more flexible than you'd typically find in C++. Also, it costs memory. You could get away from that by stuffing the method pointers in a class structure, and invent a way for each object instance to reference a class.

Community
  • 1
  • 1
unwind
  • 391,730
  • 64
  • 469
  • 606
  • 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 Sep 10 '09 at 08:00
  • 1
    @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 Sep 10 '09 at 08:03
  • 1
    My compiler fails on the second line of code: `float (*computeArea)(const ShapeClass *shape);` saying that `ShapeClass` is an unknown type. – DanielSank Aug 09 '16 at 09:01
  • @DanielSank that is due to the lack of forward declaration required by the 'typedef struct` (not shown in the example given). Because the `struct` references itself, it requires a **declaration before** it is defined. This is [explained with an example here in Lundin's answer](https://stackoverflow.com/questions/13032015/how-to-implement-a-class-in-c). Modifying the example to include the forward declaration should resolve your issue; `typedef struct ShapeClass ShapeClass; struct ShapeClass { float (*computeArea)(const ShapeClass *shape); };` – S. Whittaker Oct 31 '17 at 09:15
  • What happens when Rectangle has a function that not all Shapes do. For example, get_corners(). A circle would not implement this but a rectangle might. How do you access a function that's not a part of the parent class that you inherited from? – Otus Sep 07 '18 at 17:04
  • @Otus With this design you just add a function, e.g. `void rectangle_get_corners(RectangleClass *rect, float *xs, float *ys);`. – unwind Sep 14 '18 at 08:09
  • Is it expected for compiler to warn of "incompatible pointer type" because you are passing a pointer to RectangleClass into the shape_computeArea() which expects a pointer to a ShapeClass? – Despertar Dec 11 '22 at 20:31
33

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 c. Header for struct definition & function declarations, c 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.
/// 
jalooc
  • 1,169
  • 13
  • 23
erelender
  • 6,175
  • 32
  • 49
14

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.

L0tad
  • 574
  • 3
  • 15
Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
  • 1
    Cool! Any other recommendations for books on OOP in C? Or any other modern design techniques in C? (or embedded systems?) – Ben Gartner Sep 10 '09 at 08:22
9

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
  • 297,451
  • 125
  • 333
  • 465
8

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
  • 385
  • 2
  • 11
  • 1
    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 Sep 10 '09 at 08:20
  • 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 Sep 10 '09 at 08:31
8

I will give a simple example of how OOP should be done in C. I realize this thread is from 2009 but would like to add this anyway.

/// Object.h
typedef struct Object {
    uuid_t uuid;
} Object;

int Object_init(Object *self);
uuid_t Object_get_uuid(Object *self);
int Object_clean(Object *self);
/// Person.h
typedef struct Person {
    Object obj;
    char *name;
} Person;

int Person_init(Person *self, char *name);
int Person_greet(Person *self);
int Person_clean(Person *self);
/// Object.c
#include "object.h"

int Object_init(Object *self)
{
    self->uuid = uuid_new();

    return 0;
}
uuid_t Object_get_uuid(Object *self)
{ // Don't actually create getters in C...
    return self->uuid;
}
int Object_clean(Object *self)
{
    uuid_free(self->uuid);

    return 0;
}
/// Person.c
#include "person.h"

int Person_init(Person *self, char *name)
{
    Object_init(&self->obj); // Or just Object_init(&self);
    self->name = strdup(name);

    return 0;
}
int Person_greet(Person *self)
{
    printf("Hello, %s", self->name);

    return 0;
}
int Person_clean(Person *self)
{
    free(self->name);
    Object_clean(self);

    return 0;
}
/// main.c
int main(void)
{
    Person p;

    Person_init(&p, "John");
    Person_greet(&p);
    Object_get_uuid(&p); // Inherited function
    Person_clean(&p);

    return 0;
}

The basic concept involves placing the 'inherited class' at the top of the struct. This way, accessing the first 4 bytes in the struct also accesses the first 4 bytes in the 'inherited class' (assuming non-crazy optimizations). Now, when the pointer of the struct is cast to the 'inherited class', the 'inherited class' can access the 'inherited values' in the same way it would access its members normally.

This and some naming conventions for constructors, destructors, allocation, and deallocation functions (I recommend _init, _clean, _new, and _free) will get you a long way.

As for Virtual functions, use function pointers in the struct, possibly with Class_func(...); wrapper too.

As for (simple) templates, add a size_t parameter to determine size, require a void* pointer, or require a 'class' type with just the functionality you care about. (e.g. int GetUUID(Object *self); GetUUID(&p);)

Michael M.
  • 10,486
  • 9
  • 18
  • 34
yyny
  • 1,623
  • 18
  • 20
  • Disclaimer: All code written on smartphone. Add errorchecks where needed. Check for bugs. – yyny May 21 '16 at 00:16
4

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
  • 51,004
  • 28
  • 112
  • 141
4

GTK is built entirely on C and it uses many OOP concepts. I have read through the source code of GTK and it is pretty impressive, and definitely easier to read. The basic concept is that each "class" is simply a struct, and associated static functions. The static functions all accept the "instance" struct as a parameter, do whatever then need, and return results if necessary. For Example, you may have a function "GetPosition(CircleStruct obj)". The function would simply dig through the struct, extract the position numbers, probably build a new PositionStruct object, stick the x and y in the new PositionStruct, and return it. GTK even implements inheritance this way by embedding structs inside structs. pretty clever.

rocketsarefast
  • 4,072
  • 1
  • 24
  • 18
4
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <uchar.h>

/**
 * Define Shape class
 */
typedef struct Shape Shape;
struct Shape {
    /**
     * Variables header...
     */
    double width, height;

    /**
     * Functions header...
     */
    double (*area)(Shape *shape);
};

/**
 * Functions
 */
double calc(Shape *shape) {
        return shape->width * shape->height;
}

/**
 * Constructor
 */
Shape _Shape() {
    Shape s;

    s.width = 1;
    s.height = 1;

    s.area = calc;

    return s;
}

/********************************************/

int main() {
    Shape s1 = _Shape();
    s1.width = 5.35;
    s1.height = 12.5462;

    printf("Hello World\n\n");

    printf("User.width = %f\n", s1.width);
    printf("User.height = %f\n", s1.height);
    printf("User.area = %f\n\n", s1.area(&s1));

    printf("Made with \xe2\x99\xa5 \n");

    return 0;
};
Pierozi
  • 352
  • 2
  • 4
  • Avoid names like `_Shape`. That'd be undefined behaviour. Name starting by an underscore followed by a capital letter are [reserved identifiers](https://en.cppreference.com/w/c/language/identifier). – viraltaco_ Aug 21 '20 at 22:44
3

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

Artem Barger
  • 40,769
  • 9
  • 59
  • 81
  • 1
    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 Sep 10 '09 at 08:16
  • 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 Sep 10 '09 at 12:08
3

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
  • 14,413
  • 10
  • 36
  • 34
  • 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 Sep 10 '09 at 08:08
  • 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 Sep 10 '09 at 08:09
  • 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 Sep 10 '09 at 08:31
3

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.

Community
  • 1
  • 1
Gerhard
  • 6,850
  • 8
  • 51
  • 81
2

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
  • 32,620
  • 21
  • 85
  • 124
2

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
  • 15,593
  • 16
  • 82
  • 128
1

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.

Ether
  • 53,118
  • 13
  • 86
  • 159
Goz
  • 61,365
  • 24
  • 124
  • 204
  • I've said it already and will say it again, but will say it again: You don't need function pointers or the ability to call functions from structs C++ style to create OOP in C, OOP is mostly about inheritance of functionality and variables (content) both of which can be achieved in C without function pointers or duplicating code. – yyny May 20 '16 at 23:30
0

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 function 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.

BenMorel
  • 34,448
  • 50
  • 182
  • 322
  • 4
    I won't downvote, but FYI, function pointers, or the ability to call functions from structs (Which I suppose is your intention) has nothing to do with OOP. OOP is mostly about inheritance of functionality and variables, both of which can be achieved in C without function pointers or duplications. – yyny May 20 '16 at 23:27