Static allocation of opaque data types Static allocation of opaque data types c c

Static allocation of opaque data types


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.

// Headertypedef struct {} something;int get_size();something* create_something(void* mem);// Usagehandle* ptr = create_something(_alloca(get_size()); // or define a macro.// Implementationint 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 32real_type objects[MAX_OBJECTS];unsigned int in_use; // Make sure this is large enoughsomething* 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.


One way would be to add something like

#define MODULE_HANDLE_SIZE (4711)

to the public module.h header. Since that creates a worrying requirement of keeping this in sync with the actual size, the line is of course best auto-generated by the build process.

The other option is of course to actually expose the structure, but document it as being opaque and forbidding access through any other means than through the defined API. This can be made more clear by doing something like:

#include "module_private.h"typedef struct{  handle_private_t private;} handle_t;

Here, the actual declaration of the module's handle has been moved into a separate header, to make it less obviously visible. A type declared in that header is then simply wrapped in the desired typedef name, making sure to indicate that it is private.

Functions inside the module that take handle_t * can safely access private as a handle_private_t value, since it's the first member of the public struct.


One solution if to create a static pool of struct handle_t objects, and provide then as neceessary. There are many ways to achieve that, but a simple illustrative example follows:

// In file module.cstruct handle_t {    int foo;    void* something;    int another_implementation_detail;    int in_use ;} ;static struct handle_t handle_pool[MAX_HANDLES] ;handle_t* create_handle() {    int h ;    handle_t* handle = 0 ;    for( h = 0; handle == 0 && h < MAX_HANDLES; h++ )    {        if( handle_pool[h].in_use == 0 )        {            handle = &handle_pool[h] ;        }    }    // other initialization    return handle;}void release_handle( handle_t* handle ) {    handle->in_use = 0 ;}

There are faster faster ways of finding an unused handle, you could for example keep a static index that increments each time a handle is allocated and 'wraps-around' when it reaches MAX_HANDLES; this would be faster for the typical situation where several handles are allocated before releasing any one. For a small number of handles however, this brute-force search is probably adequate.

Of course the handle itself need no longer be a pointer but could be a simple index into the hidden pool. This would enhance data hiding and protection of the pool from external access.

So the header would have:

typedef int handle_t ;

and the code would change as follows:

// In file module.cstruct handle_s {    int foo;    void* something;    int another_implementation_detail;    int in_use ;} ;static struct handle_s handle_pool[MAX_HANDLES] ;handle_t create_handle() {    int h ;    handle_t handle = -1 ;    for( h = 0; handle != -1 && h < MAX_HANDLES; h++ )    {        if( handle_pool[h].in_use == 0 )        {            handle = h ;        }    }    // other initialization    return handle;}void release_handle( handle_t handle ) {    handle_pool[handle].in_use = 0 ;}

Because the handle returned is no longer a pointer to the internal data, and inquisitive or malicious user cannnot gain access to it through the handle.

Note that you may need to add some thread-safety mechanisms if you are getting handles in multiple threads.