C API design: Who should allocate? [closed]

Solution 1:

Another disadvantage of #2 is that the caller doesn't have control over how things are allocated. This can be worked around by providing an API for the client to register his own allocation/deallocation functions (like SDL does), but even that may not be sufficiently fine-grained.

The disadvantage of #1 is that it doesn't work well when output buffers are not fixed-size (e.g. strings). At best, you will then need to provide another function to obtain the length of the buffer first so that the caller can allocate it. At worst, it is simply impossible to do so efficiently (i.e. computing length on a separate path is overly expensive over computing-and-copying in one go).

The advantage of #2 is that it allows you to expose your datatype strictly as an opaque pointer (i.e. declare the struct but don't define it, and use pointers consistently). Then you can change the definition of the struct as you see fit in future versions of your library, while clients remain compatible on binary level. With #1, you have to do it by requiring the client to specify the version inside the struct in some way (e.g. all those cbSize fields in Win32 API), and then manually write code that can handle both older and newer versions of the struct to remain binary-compatible as your library evolves.

In general, if your structs are transparent data which will not change with future minor revision of the library, I'd go with #1. If it is a more or less complicated data object and you want full encapsulation to fool-proof it for future development, go with #2.

Solution 2:

Method number 2 every time.

Why? because with method number 1 you have to leak implementation details to the caller. The caller has to know at least how big the struct is. You can't change the internal implementation of the object without recompiling any code that uses it.

Solution 3:

Why not provide both, to get the best of both worlds?

Use _init and _terminate functions to use method #1 (or whatever naming you see fit).

Use additional _create and _destroy functions for the dynamic allocation. Since _init and _terminate already exist, it effectively boils down to:

myStruct *myStruct_create ()
{
    myStruct *s = malloc(sizeof(*s));
    if (s) 
    {
        myStruct_init(s);
    }
    return (s);
}

void myStruct_destroy (myStruct *s)
{
    myStruct_terminate(s);
    free(s);
}

If you want it to be opaque, then make _init and _terminate static and do not expose them in the API, only provide _create and _destroy. If you need other allocations, e.g. with a given callback, provide another set of functions for this, e.g. _createcalled, _destroycalled.

The important thing is to keep track of the allocations, but you have to do this anyway. You must always use the counterpart of the used allocator for deallocation.

Solution 4:

My favourite example of a well-design C API is GTK+ which uses method #2 that you describe.

Although another advantage of your method #1 is not just that you could allocate the object on the stack, but also that you could reuse the same instance multiple times. If that's not going to be a common use case, then the simplicity of #2 is probably an advantage.

Of course, that's just my opinion :)

Solution 5:

Both are functionally equivalent. But, in my opinion, method #2 is easier to use. A few reasons for prefering 2 over 1 are:

  1. It is more intuitive. Why should I have to call free on the object after I have (apparently) destroyed it using myStruct_Destroy.

  2. Hides details of myStruct from user. He does not have to worry about it's size, etc.

  3. In method #2, myStruct_init does not have to worry about the initial state of the object.

  4. You don't have to worry about memory leaks from user forgetting to call free.

If your API implementation is being shipped as a separate shared library however, method #2 is a must. To isolate your module from any mismatch in implementations of malloc/new and free/delete across compiler versions you should keep memory allocation and de-allocation to yourself. Note, this is more true of C++ than of C.