Static allocation of opaque data types

Bart picture Bart · Dec 14, 2010 · Viewed 7.5k times · Source

Very often malloc() is absolutely not allowed when programming for embedded systems. Most of the time I'm pretty able to deal with this, but one thing irritates me: it keeps me from using so called 'opaque types' to enable data hiding. Normally I'd do something like this:

// In file module.h
typedef struct handle_t handle_t;

handle_t *create_handle();
void operation_on_handle(handle_t *handle, int an_argument);
void another_operation_on_handle(handle_t *handle, char etcetera);
void close_handle(handle_t *handle);


// In file module.c
struct handle_t {
    int foo;
    void *something;
    int another_implementation_detail;
};

handle_t *create_handle() {
    handle_t *handle = malloc(sizeof(struct handle_t));
    // other initialization
    return handle;
}

There you go: create_handle() performs a malloc() to create an 'instance'. A construction often used to prevent having to malloc() is to change the prototype of create_handle() like this:

void create_handle(handle_t *handle);

And then the caller could create the handle this way:

// In file caller.c
void i_am_the_caller() {
    handle_t a_handle;    // Allocate a handle on the stack instead of malloc()
    create_handle(&a_handle);
    // ... a_handle is ready to go!
}

But unfortunately this code is obviously invalid, the size of handle_t isn't known!

I never really found a solution to solve this in a proper way. I'd very like to know if anyone has a proper way of doing this, or maybe a complete different approach to enable data hiding in C (not using static globals in the module.c of course, one must be able to create multiple instances).

Answer

Puppy picture Puppy · Dec 14, 2010

You can use the _alloca function. I believe that it's not exactly Standard, but as far as I know, nearly all common compilers implement it. When you use it as a default argument, it allocates off the caller's stack.

// Header
typedef struct {} something;
int get_size();
something* create_something(void* mem);

// Usage
handle* ptr = create_something(_alloca(get_size()); // or define a macro.

// Implementation
int get_size() {
    return sizeof(real_handle_type);
}
something* create_something(void* mem) {
    real_type* ptr = (real_type_ptr*)mem;
    // Fill out real_type
    return (something*)mem;
}

You could also use some kind of object pool semi-heap - if you have a maximum number of currently available objects, then you could allocate all memory for them statically, and just bit-shift for which ones are currently in use.

#define MAX_OBJECTS 32
real_type objects[MAX_OBJECTS];
unsigned int in_use; // Make sure this is large enough
something* create_something() {
     for(int i = 0; i < MAX_OBJECTS; i++) {
         if (!(in_use & (1 << i))) {
             in_use &= (1 << i);
             return &objects[i];
         }
     }
     return NULL;
}

My bit-shifting is a little off, been a long time since I've done it, but I hope that you get the point.