What is the proper/preferred way to allocate memory in a C API?
I can see, at first, two options:
1) Let the caller do all the (outer) memory handling:
myStruct *s = malloc(sizeof(s)); myStruct_init(s); myStruct_foo(s); myStruct_destroy(s); free(s);
The _init
and _destroy
functions are necessary since some more memory may be allocated inside, and it must be handled somewhere.
This has the disadvantage of being longer, but also the malloc can be eliminated in some cases (e.g., it can be passed a stack-allocated struct:
int bar() { myStruct s; myStruct_init(&s); myStruct_foo(&s); myStruct_destroy(&s); }
Also, it's necessary for the caller to know the size of the struct.
2) Hide malloc
s in _init
and free
s in _destroy
.
Advantages: shorter code, since the functions are going to be called anyway. Completely opaque structures.
Disadvantages: Can't be passed a struct allocated in a different way.
myStruct *s = myStruct_init(); myStruct_foo(s); myStruct_destroy(foo);
I'm currently leaning for the first case; then again, I don't know about C API design.
“calloc” or “contiguous allocation” method in C is used to dynamically allocate the specified number of blocks of memory of the specified type.
In C, dynamic memory is allocated from the heap using some standard library functions. The two key dynamic memory functions are malloc() and free(). The malloc() function takes a single parameter, which is the size of the requested memory area in bytes. It returns a pointer to the allocated memory.
Dynamic allocation is required when you don't know the worst case requirements for memory. Then, it is impossible to statically allocate the necessary memory, because you don't know how much you will need. Even if you know the worst case requirements, it may still be desirable to use dynamic memory allocation.
It is a dynamic memory allocation function which is used to allocate the memory to complex data structures such as arrays and structures. Malloc() function is used to allocate a single block of memory space while the calloc() in C is used to allocate multiple blocks of memory space.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With