Home > OS >  Class model in C
Class model in C

Time:08-06

I'm trying to do a "class model" in C in which I define a struct that represents the class and inside it I define function pointers to represent methods, like this:

//ClassName.h

typedef struct struct_ClassName ClassName;

struct ClassName
{

    
    char a;

    char b;

    char c;

    

    void (*method1)(ClassName*, char);

    void (*method2)(ClassName*, char);

    ...

    void (*methodN)(ClassName*, char);

};

void initClassName(ClassName*);

//ClassName.c

#include "ClassName.h"

static void method1(ClassName *this_c, char c);
static void method2(ClassName *this_c, char c);
...
static void methodN(ClassName *this_c, char c);

void initClassName(ClassName *this_c)
{
    this_c->method1 = &method1;
    this_c->method2 = &method2;
    ...
    this_c->methodN = &methodN;
}

void method1(ClassName *this_c, char c)
{
    //do something
}

void method2(ClassName *this_c, char c)
{
    //do something
}

...


void methodN(ClassName *this_c, char c)
{
    //do something
}

Everything works fine but, somewhere in the code, I define an array:

...

ClassName objects[200];

for(i = 0; i < 200; i  )
{
    initClassName(&objects[i]);
}
...

Because of the function pointers, the memory usage associated to this array is quite high (I have several "methods" in this "class").

Considering that this is a code that will run in an embedded system, is there a better way to do it?

Defining functions outside the structure could be a possibility but in my opinion it does not respect what I'm trying to emulate.

I have to use only C, not C .

CodePudding user response:

What you have created is a very dynamic system where each object instance can have its own set of method implementations. That's why it uses so much memory.

Another approach is an implementation closer to early C (before multiple inheritance) where all instance of the same class share the same vtable. The vtable contains the function pointers.

typedef struct struct_ClassName ClassName;

typedef struct struct_ClassName_vtable ClassName_vtable;

struct ClassName_vtable
{
    void (*method1)(ClassName*, char);
    void (*method2)(ClassName*, char);
    ...
    void (*methodN)(ClassName*, char);
}

struct ClassName
{
    ClassName_vtable* _vtable;
    
    char a;
    char b;
    char c;
};

void initClassName(ClassName*);
static void method1(ClassName *this_c, char c);
static void method2(ClassName *this_c, char c);
...
static void methodN(ClassName *this_c, char c);

ClassName_vtable _ClassName_vtable = {
    method1,
    method2,
    ...,
    methodN
};

void initClassName(ClassName *this_c)
{
    this_c->_vtable = _ClassName_vtable;
}

That way, the OO overhead per instance is only the size of a pointer. It's also easier to create subclasses.

A method call looks like this:

ClassName* obj = ...;
obj->vtable->method2(obj, 'a');

CodePudding user response:

ClassName objects[200];

for(i = 0; i < 200; i  )
    initClassName(&objects[i]);

I will show you a stripped-off version of something I use here for similar effect. It is hard to say when a certain size is huge in terms of pointers or whatever. Each environment has their truth and this can be useless or useful...

Anyway, the ideia is encapsulation. And in C we have no this pointer. C is not C or java or javascript. Each class instance must have a table of function pointers for the methods. We need to build these tables. This is how virtual classes are implemented in others languages anyway. And if each class element can allocate memory code is needed to allocate and free memory.

TL;DR

Below is a C example of a program that builds and uses an array of classes. In this case an array of stacks. It shows a mechanism of building stacks of mixed things. Each array item has all that is needed to manage his own stack instance, be it of a trivial type or a complex structure. It can be easily changed to implement other tyoes of classes in C.

Please do not bother warning me that I cast the return of malloc(). I , as many others, do not like implicit things. And I know that C-FAQ is a never-updated thing from the end of the '90s so no need to reference this either.

An example: a static STACK container

typedef struct
{
    int data[SIZE];
    int limit; // capacity  
    int size; // actual
}   Stack;

This is it: a simple stack of int values. Let us say we want to declare a vector of stacks of different things, but in C. And use methods on them. If we use trivial types --- in C we say the struct is trivialy constructible --- things can get easier, but if we are about to use structs we need to know about how to manipulate stack elements, since they can allocate memory. We are writing a container so the methods of the class must work for any underlying data. And we have no iterators like C STL. Here we are implementing the POP TOP and PUSH methods for stacks, and a toString() method like in java to print values on the screen.

For each possible content in the container we need to have a constructor, a destructor, a copy constructor and a display method. In this example we have just 2 types of stacks: a stack of int and a stack of struct Sample:

typedef struct
{
    size_t _id;
    char   name[30];
    char   phone[20];
} Sample;

We can add others just by writing the required 4 functions.

main.c example

int main(void)
{
    srand(220804); // for the factory functions

    Stack* class_array[2] = {
        create(4, create_i, copy_i, destroy_i, show_i),
        create(3, create_st, copy_st, destroy_st, show_st)};

    printf("\n\n=====> Testing with STACK of int\n\n");
    class_test(class_array[0], factory_i);
    printf(
        "\n\n=====> Testing with STACK of struct "
        "Sample\n\n");
    class_test(class_array[1], factory_st);

    class_array[0]->destroy(class_array[0]);
    class_array[1]->destroy(class_array[1]);

    return 0;
}

Each instance of Stack has pointers to the stack methods and to the functions that manipulate the stack data, so we can have a single class_test() function that does the following:

  • builds a stack of the required size, 4 or 3 in the example
  • fills the stack with data generated by factory functions (in production the logic builds the data)
  • shows the stack contents
  • removes all stack elements, one by one

At the end the destructor is called for eack stack.

The class.h file


typedef void* (PVFV)(void*);
typedef int   (PIFV)(void*);

typedef struct
{
    size_t size_;
    size_t lim_;
    void** data_;

    PVFV* copy;
    PVFV* destroy;
    int (*show)(void*,const char*); // for testing
    // constructor and destructor for container elements
    PVFV* create_1;
    PVFV* copy_1;
    PVFV* destroy_1;
    PIFV* show_1;
    // member functions
    PIFV* POP;
    int (*PUSH)(void*,void*);
    PVFV* TOP;
    PIFV* empty;
    size_t (*size)(void*);

} Stack;

Stack* create(
    size_t, 
    void* (*)(void*),
    void* (*)(void*),
    void* (*)(void*),
    int   (*)(void*));

int class_test(Stack*, void* (*)());

the example output

=====> Testing with STACK of int

Stack is empty
POP() on empty stack returned -2
TOP() on empty stack returned NULL
Calls PUSH until error

Value inserted: 42
Value inserted: 41
Value inserted: 40
Value inserted: 39
Stack now has 4 elements

Stack has 4 of 4 elements:

 42
 41
 40
 39


Calls POP() until error
Stack size: 3
Stack size: 2
Stack size: 1
Stack size: 0


=====> Testing with STACK of struct Sample

Stack is empty
POP() on empty stack returned -2
TOP() on empty stack returned NULL
Calls PUSH until error

Value inserted:  0195   Sample id#0195       76(203)6840-195
Value inserted:  0943   Sample id#0943       35(686)9368-943
Value inserted:  0152   Sample id#0152       16(051)8816-152
Stack now has 3 elements

Stack has 3 of 3 elements:

  0096   Sample id#0096       24(477)0418-096
  0037   Sample id#0037       27(214)3509-037
  0836   Sample id#0836       68(857)4634-836


Calls POP() until error
Stack size: 2
Stack size: 1
Stack size: 0

the logic

For each tye of element we need to write the 4 functions: they can alocate memory and be very complex or they can be trivial, but the class methods need to handle any case.

code for struct Sample in stack_struct.h###

#pragma once
#include <memory.h>
#include <stdio.h>
#include <stdlib.h>

void* copy_st(void*);
void* create_st(void*);
void* destroy_st(void*);
void* factory_st();

typedef struct
{
    size_t _id;
    char   name[30];
    char   phone[20];
} Sample;

void* create_st(void* el)
{
    return factory_st();
}

void* copy_st(void* el)
{
    if (el == NULL) return NULL;
    Sample* e = (Sample*)malloc(sizeof(Sample));
    *e     = *((Sample*)el);
    return e;
}

void* destroy_st(void* el)
{
    if (el == NULL) return NULL;
    free(el);
    return NULL;
}

int show_st(void* el)
{
    if (el == NULL) return 0;
    Sample* e = (Sample*)el;
    printf(
        "  d  s   s\n",
        (int) e->_id, e->name, e->phone);
    return 0;
}

void* factory_st()
{
    Sample* e = (Sample*)malloc(sizeof(Sample));
    e->_id    = rand() % 1000;
    sprintf(e->name, "Sample id#d", (int)e->_id);
    memset(e->phone, 0, sizeof(e->phone));
    e->phone[0] = ' ';
    for (int i = 1; i <= 17; i  = 1)
        e->phone[i] = '0'   rand() % 10;
    e->phone[3]  = '(';
    e->phone[7]  = ')';
    e->phone[12] = '-';
    e->phone[13] = e->name[11];
    e->phone[14] = e->name[12];
    e->phone[15] = e->name[13];
    e->phone[16] = e->name[14];
    e->phone[17] = 0;
    return (void*)e;
}

code for int elements stack_int.h_###

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

void* create_i(void* el)
{
    int* e = (int*)malloc(sizeof(int));
    *e     = *((int*)el);
    return (void*)e;
}

void* copy_i(void* el)
{
    if (el == NULL) return NULL;
    int* e = (int*)malloc(sizeof(int));
    *e     = *( (int*)el );
    return e;
}

void* destroy_i(void* el)
{
    if (el == NULL) return NULL;
    free(el);
    return NULL;
}

int show_i(void* el)
{
    if (el == NULL) return 0;
    int v = *((int*)el);
    printf(" %d\n", v);
    return 0;
}

void* factory_i()
{
    static int i       = 42;
    int*       new_int = (int*)malloc(sizeof(int));
    *new_int           = i;
    i -= 1;
    return (void*)new_int;
}

The class implementation class.c

#include "class.h"

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

void*  Copy__(void*);
void*  Destroy__(void*);
int    POP__(void*);
int    PUSH__(void*, void*);
int    Show__(void*, const char*);
void*  TOP__(void*);
int    empty__(void*);
size_t size__(void*);

Stack* create(
    size_t sz, void* (*create)(void*), void* (*copy)(void*),
    void* (*destroy)(void*), int (*show)(void*))
{
    Stack* stack = (Stack*)malloc(sizeof(Stack));
    if (stack == NULL) return NULL;
    stack->size_ = 0;
    stack->lim_  = sz;
    stack->data_ = (void*)malloc(sz * sizeof(void*));

    stack->copy    = Copy__;
    stack->destroy = Destroy__;
    stack->show    = Show__;

    stack->create_1  = create;
    stack->copy_1    = copy;
    stack->destroy_1 = destroy;
    stack->show_1    = show;

    stack->POP  = POP__;
    stack->PUSH = PUSH__;
    stack->TOP  = TOP__;

    stack->empty = empty__;
    stack->size  = size__;

    return stack;
}

void* Copy__(void* one) { return NULL; };

void* Destroy__(void* stack)
{  // before destructing a stack we need to
    // destroy all elements
    if (stack == NULL) return NULL;
    Stack* st = (Stack*)stack;
    for (size_t ix = 0; ix < st->size_; ix  = 1)
        (st->destroy_1)(st->data_[ix]);
    free(st->data_);
    free(st);
    return NULL;
};
int POP__(void* stack)
{
    if (stack == NULL) return -1;  // no stack
    Stack* st = stack;
    if (st->size_ == 0) return -2;  // empty
    st->size_ -= 1;                 // one less
    return 0;                       // ok
}

int PUSH__(void* el, void* stack)
{
    if (el == NULL) return -1;     // no element
    if (stack == NULL) return -2;  // no stack
    Stack* st = (Stack*)stack;
    if (st->size_ == st->lim_) return -3;  // full
    void* new_el = st->create_1(el);       // copy construct
    st->data_[st->size_] = new_el;
    st->size_  = 1;  // one up
    return 0;        // ok
}

int Show__(void* stack, const char* title)
{
    if (stack == NULL) return -1;
    Stack* st = stack;
    if (title != NULL) printf("%s\n", title);
    if (st->size_ == 0)
    {
        printf("Stack is empty\n");
        return 0;
    }
    for (size_t ix = 0; ix < st->size_; ix  = 1)
        st->show_1(st->data_[ix]);
    printf("\n");
    return 0;
}

void* TOP__(void* stack)
{
    if (stack == NULL) return NULL;  // no stack
    Stack* st = stack;
    if (st->size_ == 0) return NULL;  // empty
    return st->data_[st->size_ - 1];  // ok
}

int empty__(void* stack)
{
    if (stack == NULL) return 1;  // empty??
    return ((Stack*)stack)->size_ == 0;
}

size_t size__(void* stack)
{
    if (stack == NULL) return 1;  // empty??
    return ((Stack*)stack)->size_;
}


///////////// TEST FUNCTION ///////////////

int class_test(Stack* tst, void* (*factory)())
{
    if (tst == NULL) return -1;
    // is stack empty?
    if (tst->empty(tst))
        printf("Stack is empty\n");
    else
        printf("Stack: %zd elements\n", tst->size(tst));

    int res = tst->POP(tst);
    printf("POP() on empty stack returned %d\n", res);

    void* top = tst->TOP(tst);
    if (top == NULL)
        printf("TOP() on empty stack returned NULL\n");
    else
    {
        printf(
            "\nTOP() on empty stack returned NOT NULL!\n");
        return -2;
    }

    printf("Calls PUSH until error\n\n");
    void* one   = factory();
    int   value = *(int*)one;
    while (tst->PUSH(one, tst) == 0)
    {
        printf("Value inserted:");
        tst->show_1(one);
        free(one);
        one = factory();
    }
    free(one);  // last one, not inserted
    printf("Stack now has %zd elements\n", tst->size(tst));

    char title[80] = {" "};
    sprintf(
        title, "\nStack has %zd of %zd elements:\n",
        tst->size_, tst->lim_);
    tst->show(tst, title);

    // agora esvazia a pilha ate dar erro
    printf("\nCalls POP() until error\n");
    while (tst->POP(tst) == 0)
        printf("Stack size: %I32d\n", (int)tst->size(tst));
    return 0;
};

The complete main.c program

#include <stdio.h>

#include "class.h"
#include "stack_int.h"
#include "stack_struct.h"

int main(void)
{
    srand(220804);

    Stack* class_array[2] = {
        create(4, create_i, copy_i, destroy_i, show_i),
        create(3, create_st, copy_st, destroy_st, show_st)};

    printf("\n\n=====> Testing with STACK of int\n\n");
    class_test(class_array[0], factory_i);
    printf(
        "\n\n=====> Testing with STACK of struct "
        "Sample\n\n");
    class_test(class_array[1], factory_st);

    class_array[0]->destroy(class_array[0]);
    class_array[1]->destroy(class_array[1]);

    return 0;
}
  • Related