Whenever I see a C "class" (any struct that is meant to be used by accessing functions that take a pointer to it as the first argument) I see them implemented like this:
typedef struct { int member_a; float member_b; } CClass; CClass* CClass_create(); void CClass_destroy(CClass *self); void CClass_someFunction(CClass *self, ...); ...
And in this case CClass_create
always malloc
s it's memory and returns a pointer to that.
Whenever I see new
come up in C++ unnecessarily, it usually seems to drive C++ programmers crazy, but this practice seems acceptable in C. What gives? Is there some reason behind why heap-allocated struct "classes" are so common?
Local struct (value type) variables are stored on the stack, value type fields of a class are stored on the heap.
Use the stack when your variable will not be used after the current function returns. Use the heap when the data in the variable is needed beyond the lifetime of the current function.
The stack is faster because the access pattern makes it trivial to allocate and deallocate memory from it (a pointer/integer is simply incremented or decremented), while the heap has much more complex bookkeeping involved in an allocation or free.
There are several reasons for this.
Let's discuss them briefly.
For opaque pointers, it enables you to do something like:
struct CClass_; typedef struct CClass_ CClass; // the rest as in your example
So, the user doesn't see the definition of struct CClass_
, insulating her from the changes to it and enabling other interesting stuff, like implementing the class differently for different platforms.
Of course, this prohibits using stack variables of CClass
. But, OTOH, one can see that this doesn't prohibit allocating CClass
objects statically (from some pool) - returned by CClass_create
or maybe another function like CClass_create_static
.
Lack of destructors - since C compiler will not automatically destruct your CClass
stack objects, you need to do it yourself (manually calling the destructor function). So, the only benefit left is the fact that stack allocation is, in general, faster than heap allocation. OTOH, you don't have to use the heap - you can allocate from a pool, or an arena, or some such thing, and that may be almost as fast as stack allocation, without the potential problems of stack allocation discussed below.
Embedded systems - Stack is not an "infinite" resource, you know. Sure, for most applications on today's "Regular" OSes (POSIX, Windows...), it almost is. But, on embedded systems, stack may be as low as a few KBs. That's extreme, but even "big" embedded systems have stack that are in MBs. So, it will run out if over-used. When it does, mostly there is no guarantee what will happen - AFAIK, in both C and C++ that's "Undefined behaviour". OTOH, CClass_create()
can return NULL pointer when you're out of memory, and you can handle that.
Containers - C++ users like stack allocation, but, if you create a std::vector
on stack, its contents will be heap allocated. You can tweak that, of course, but that is the default behaviour, and it makes ones life much easier to say "all members of a container are heap-allocated" rather than trying to figure out how to handle if they are not.
Inertia - well, the OO came from SmallTalk. Everything is dynamic there, so, the "natural" translation to C is the "put everything on the heap" way. So, the first examples were like that and they inspired others for many years.
"Laziness" - if you know you only want stack objects, you need something like:
CClass CClass_make(); void CClass_deinit(CClass *me);
But, if you want to allow both stack and heap, you need to add:
CClass *CClass_create(); void CClass_destroy(CClass *me);
This is more work to do for the implementer, but is also confusing to the user. One can make slightly different interfaces, but it doesn't change the fact that you need two sets of functions.
Of course, the "containers" reason is also partially a "laziness" reason.
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