Search code examples
cembeddedopaque-pointers

Static allocation of opaque data types


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


Solution

  • 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;
    size_t get_size();
    something* create_something(void* mem);
    
    // Usage
    something* ptr = create_something(_alloca(get_size())); // or define a macro.
    
    // Implementation
    size_t get_size() {
        return sizeof(real_handle_type);
    }
    something* create_something(void* mem) {
        real_handle_type* ptr = (real_handle_type*)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.