48

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 mallocs 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?

trincot
  • 317,000
  • 35
  • 244
  • 286
Therhang
  • 825
  • 1
  • 9
  • 15
  • 8
    Funny, I tend to create most of my objects on the stack, including `struct`s. – EOF Jul 28 '15 at 10:04
  • 7
    One reason may be the ability to hide the interface (i.e. forward-declare the struct). However, I myself prefer to avoid superfluous dynamic allocation – recently, when I was writing a C interface to a scripting language, I had to decide between a heap and stack allocated "context" object, and I went with the automatic allocation because there was no real need for dynamic allocation, nor a real benefit of trying to hide its implementation. – The Paramagnetic Croissant Jul 28 '15 at 10:04
  • It's my understanding that it's an attempt to reap as many of the rewards of Object-Oriented Programming as possible in a language that doesn't have built-in support for standard OOP features. – ApproachingDarknessFish Jul 28 '15 at 10:10
  • 5
    Well, you have to with this design. Not necessary, it is just missing void CClass_initialize(CClass* self) to make it more universal. – Hans Passant Jul 28 '15 at 10:13
  • @TheParamagneticCroissant there's always a benefit of *information hiding* and *single responsibility*: avoiding bugs. When your project has the potential to grow over time, this can be an issue. So, your design decision is good **iff** you can be sure that the scope of your project **won't** broaden over time. –  Jul 28 '15 at 12:07
  • 1
    @FelixPalmen Of course that's a nice theoretical formulation, but if someone pokes around with the representation of my struct despite the docs, comments, etc. and everything else telling him to only use accessor functions, then quite frankly I don't care what will be broken for him. Discipline is important. – The Paramagnetic Croissant Jul 28 '15 at 12:10
  • 1
    @TheParamagneticCroissant you're right, documenting what is *private* should be enough (as long as you're not concerned about unnecessary recompilation of consuming code). It's just *opaque pointers* will set the barrier to do something unintended much higher (means: casting of pointers and other *code smell*) **edit:** after all, it remains a *design decision* –  Jul 28 '15 at 12:14
  • 4
    Note that this question is actually about creating **objects**, not classes. It's a common shorthand to refer to objects this way, but it's often confusing, especially in discussions about fundamentals. – Pete Becker Jul 28 '15 at 13:09
  • 1
    A lot of things C programmers do drive people crazy, which I suppose is one of the reasons C++ exists in the first place. – Kerrek SB Jul 28 '15 at 17:51

12 Answers12

51

There are several reasons for this.

  1. Using "opaque" pointers
  2. Lack of destructors
  3. Embedded systems (stack overflow problem)
  4. Containers
  5. Inertia
  6. "Laziness"

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.

srdjan.veljkovic
  • 2,468
  • 16
  • 24
  • 3
    +1 for completeness, can't see anything missing here. For more elaboration on *opaque pointers* benefits, see my answer. –  Jul 28 '15 at 12:42
  • It doesn't have to be all that confusing, because you can be quite rigorous about providing the same interface to all the "classes" you write: using the above terminology `create` does `malloc` and `make`, while `destroy` does `deinit` and `free`. But sure, it's that little bit of extra boilerplate that nobody can be bothered with until the need is proven. – Steve Jessop Jul 28 '15 at 21:20
  • Your `make`/`deinit` paradigm doesn't necessarily force you into keeping the objects on the stack - it just means the caller is responsible for allocating / deallocating the memory, whether stack or heap. You can certainly do `CClass *p = malloc(...); *p = CClass_make(); ...; CClass_deinit(p); free(p);`. – Nate Eldredge Aug 03 '15 at 00:40
14

Assuming, as in your question, CClass_create and CClass_destroy use malloc/free, then for me doing following is bad practice:

void Myfunc()
{
  CClass* myinstance = CClass_create();
  ...

  CClass_destroy(myinstance);
}

because we could avoid a malloc and a free easily:

void Myfunc()
{
  CClass myinstance;        // no malloc needed here, myinstance is on the stack
  CClass_Initialize(&myinstance);
  ...

  CClass_Uninitialize(&myinstance);
                            // no free needed here because myinstance is on the stack
}

with

CClass* CClass_create()
{
   CClass *self= malloc(sizeof(CClass));
   CClass_Initialize(self);
   return self;
}

void CClass_destroy(CClass *self);
{
   CClass_Uninitialize(self);
   free(self);
}

void CClass_Initialize(CClass *self)
{
   // initialize stuff
   ...
}

void CClass_Uninitialize(CClass *self);
{
   // uninitialize stuff
   ...
}

In C++ we'd also rather do this:

void Myfunc()
{
  CClass myinstance;
  ...

}

than this:

void Myfunc()
{
  CClass* myinstance = new CCLass;
  ...

  delete myinstance;
}

In order to avoid an unnecessary new/delete.

haccks
  • 104,019
  • 25
  • 176
  • 264
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
9

In C, when some component provides a "create" function, the component implementer is also in control over how the component is initialised. So it not only emulates C++' operator new but also the class constructor.

Giving up on this control over initialisation means a lot more error checking on the inputs, so keeping control makes it easier to provide consistent and predictable behaviour.

I also take exception to malloc always being used to allocate memory. This may often be the case, but not always. For instance, in some embedded systems, you'll find that malloc/free is not used at all. The X_create functions can allocate in other ways, e.g. from an array whose size is fixed at compile-time.

Sigve Kolbeinson
  • 1,133
  • 1
  • 7
  • 16
8

This spawns a lot of answers because it is somewhat opinion-based. Still I want to explain why I personally prefer to have my "C Objects" allocated on the heap. The reason is to have my fields all hidden (speak: private) from consuming code. This is called an opaque pointer. In practice, it means your header file doesn't define the struct in use, it only declares it. As a direct consequence, consuming code can't know the size of the struct and therefore stack allocation becomes impossible.

The benefit is: consuming code can never ever depend on the definition of the struct, that means it's impossible that you somehow render the contents of the struct inconsistent from the outside and you avoid unnecessary recompilation of consuming code when the struct changes.

The first issue is addressed in by declaring fields to be private. But the definition of your class is still imported in all compilation units that use it, making it necessary to recompile them, even when only your private members change. The solution often used in is the pimpl pattern: have all private members in a second struct (or: class) that is only defined in the implementation file. Of course, this requires your pimpl to be allocated on the heap.

Adding to that: modern OOP languages (like e.g. or ) have means to allocate objects (and typically decide whether it's stack or heap internally) without the calling code knowing about their definition.

  • 1
    One technique that was historically sometimes useful was to have a "public" structure type that contained an `int[]` of suitable size and an internal-use structure type that contained the actual data. It was still necessary for client code to know the size of the structure, but the guts would be shielded from the client code. I don't know of any way to accomplish that without requiring behaviors not mandated by the Standard, however, and some of today's compilers seek to "optimize out" anything the Standard doesn't require them to keep in. – supercat Jul 28 '15 at 21:19
  • @supercat I'd first suggest to make it a `char[]` because a) you have the unit of `sizeof()` this way and b) a `char` can legally alias any type. But then you have a portability problem: the members of the struct may have different sizes on different platforms AND alignment requirements may differ, leading to different padding. Just using `char impl[sizeof(myImplStruct)]` would defeat the concept because you'd again have to show your struct. So, in general, real *information hiding* in [tag:c] (as well es in [tag:c++]) is only possible with heap allocation. –  Jul 28 '15 at 23:06
  • The code I'd seen using that approach used `int[]` because of alignment issues; that in the 1990s, in the days before C99 and the Strict Aliasing rule. Unless client code actually uses the array as a means of accessing the underlying structure, however, I don't think the type of the array in the structure matters. Further, if the build system could recognize that the structure was declared differently in different translation units it would have no obligations at all under the C Standard, and if it couldn't recognize that it probably wouldn't be able to... – supercat Jul 28 '15 at 23:15
  • @supercat well, that's at least "easy to break" ... If it was really used sometimes, I guess that was back in the days when the overhead of `malloc` mattered more often than it does nowadays. –  Jul 28 '15 at 23:19
  • ...simultaneously look at the client and implementation code so as to notice any aliasing violations. I have a feeling that many of the people responsible for the direction of the C language weren't programmers in the 1980s and 1990s. Back then, getting good performance meant that applications needed to use a hybrid of application-programming and systems-programming methodologies, with heavy reliance upon behaviors that weren't mandated by the Standard, but were nonetheless widely supported. Unfortunately, those pushing the language standards seem to take the view that code which... – supercat Jul 28 '15 at 23:25
  • ...relied upon behaviors which would work with every decent compiler for any halfway-modern machine, but which wasn't mandated by the standard, should be viewed as "defective", whether or not the Standard defined any strictly-conformant way to meet the same behavioral requirements as efficiently. – supercat Jul 28 '15 at 23:28
  • @supercat ... the balance between *efficiency* and *optimization* on the one hand and *correctness* and *managability* on the other hand changed quite heavily over the last 20-or-so years... having the guarantee that your code behaves as intended on every possible platform nowadays is much more worthwile than saving some CPU cycles. (in general, of course, there are corner cases) –  Jul 28 '15 at 23:32
  • I would say that the reason that `(x<>(32-y))` is not equivalent to `(x<>(31-y)>>1)` is not that the former should ever have been deemed "defective", but that the Standard is defective in failing to require that on an N-bit machine, `x>>N` must either be evaluated as `(x>>(N-1))>>1` or x, chosen in Unspecified fashion. I would further suggest that there are countless other situations in which what used to be 99% portable code is clearer and more readable than any "100% portable" alternative. – supercat Jul 29 '15 at 13:01
  • Indeed, I don't think I've ever used any platform where such a guarantee wouldn't hold (with *zero* extra cost) in the absence of a reckless compiler writer deciding that it was more important to save CPU resources by eliminating code that was only needed for inputs that would result in an `int` being shifted by 32, than to ensure that a program would work as the programmer intended when given such inputs. – supercat Jul 29 '15 at 13:05
3

In general, the fact that you see a * does not mean it has been malloc'd. You could have got a pointer to static global variable, for instance; in your case, indeed, CClass_destroy() does not take any parameter which supposes it already knows some information about the object being destroyed.

Moreover, pointers, whether or not malloc'd, are the only way that allow you to modify the object.

I don't see particular reasons for using the heap instead of the stack: you don't get less memory used. What's needed, though, to initialize such "classes" are init/destroy functions because the underlying data structure may actually need to contain dynamic data, therefore the use of pointers.

edmz
  • 8,220
  • 2
  • 26
  • 45
3

I would change the "constructor" to a void CClass_create(CClass*);

It won't return an instance/reference of the struct, but be called on one.

As of whether it is allocated on the "stack" or dynamically, it depends entirely on your usage scenario requirements. However you allocate it, you just call CClass_create() passing the allocated struct as a parameter.

{
    CClass stk;
    CClass_create(&stk);

    CClass *dyn = malloc(sizeof(CClass));
    CClass_create(dyn);

    CClass_destroy(&stk); // the local object lifetime ends here, dyn lives on
}

// and later, assuming you kept track of dyn
CClass_destroy(dyn); // destructed
free(dyn); // deleted

Just be careful not to return a reference to a local (allocated on the stack), because that's UB.

However you allocate it, you will need to call void CClass_destroy(CClass*); in the right spot (the end of that object's lifetime that is), and if dynamically allocated, also free that memory.

Distinguish between allocation/deallocation and construction/destruction, those are not the same (even if in C++ they might be automatically coupled together).

dtech
  • 47,916
  • 17
  • 112
  • 190
2

C lacks certain things that C++ programmers take for granted viz.

  1. public and private specifiers
  2. constructors and destructors

The big advantage of this approach is that you can hide the struct in your C file and force the correct construction and destruction with your create and destroy functions.

If you expose the struct in your .h file, this will means users can access the members directly which breaks encapsulation. Also not forcing the create allows incorrect construction of your object.

doron
  • 27,972
  • 12
  • 65
  • 103
2

Because a function can only return a stack allocated struct if it contains no pointers to other allocated structs. If it only contain simple objects (int, bool, floats, chars and arrays of them but no pointer) you can allocate it on stack. But you must know that if you return it, it will be copied. If you want to allow pointers to other structs, or want to avoid the copy then use heap.

But if you can create the struct in a top level unit and only use it in called functions and never return it, then stack is appropriate

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
2

If the maximum number of objects of some type that will need to exist simultaneously is fixed, the system will need to be able to do something with every "live" instance, and the items in question don't consume too much money, the best approach is generally neither heap allocation nor stack allocation, but rather a statically-allocated array, along with "create" and "destroy" methods. Using an array will avoid the need to maintain a linked list of objects, and will make it possible to handle the case where an object can't be destroyed immediately because it's "busy" [e.g. if data is arriving on a channel via interrupt or DMA when user code decides it's no longer interested in the channel and disposes of it, the user code can set a "dispose when done" flag and return without having to worry about having a pending interrupt or DMA overwrite storage which is no longer allocated to it].

Using a fixed-sized pool of fixed-sized objects makes allocation and de-allocation much more predictable than taking storage from a mixed-sized heap. The approach isn't great in cases where demand is variable and the objects take up a lot of space (individually or collectively), but when demand is mostly consistent (e.g. an application needs 12 objects all the time, and sometimes needs up to 3 more) it can work out much better than alternative approaches. The one weakness is that any setup must either be performed at the place where the static buffer is declared, or must be performed by executable code in the clients. There's no way to use variable-initialization syntax at a client site.

Incidentally, when using this approach there's no need to have client code receive pointers to anything. Instead, one can identify the resources using whatever size integer is convenient. Additionally, if the number of resources will never have to exceed the number of bits in an int, it may be helpful to have some status variables use one bit per resource. For example, one could have variables timer_notifications (written only via interrupt handler) and timer_acks (written only via mainline code) and specify that bit N of (timer_notifications ^ timer_acks) will be set whenever timer N wants service. Using such an approach, code need only read two variables to determine whether any timer needs service, rather than having to read one variable for each timer.

supercat
  • 77,689
  • 9
  • 166
  • 211
1

Is your question "why in C it's normal to allocate memory dynamically and in C++ it's not"?

C++ has a lot of constructs in place that make new redundant. copy, move and normal constructors, destructors, the standard library, allocators.

But in C you cannot get around it.

Serve Laurijssen
  • 9,266
  • 5
  • 45
  • 98
  • 2
    Everything you can do with memory in C++ you can also do in C. As other answers point out above, there's *no requirement* for these functions to be backed by `malloc`/`free`, and assuming that they are is a big mistake. They could easily be using something just as fast as stack allocation. – Alex Celeste Jul 29 '15 at 17:26
1

Its actually a backlash to C++ making "new" too easy.

In theory, using this class construction pattern in C is identical to using "new" in C++, so there should be no difference. However, the way people tend to think about the languages is different, so the way people react to the code is different.

In C it is very common to think about the exact operations the computer will have to do to accomplish your goals. It's not universal, but it is a very common mindset. It is assumed that you have taken the time to do the cost/benefit analysis of the malloc/free.

In C++, it has gotten much easier to write lines of code which do a great deal for you, without you even realizing it. It is quite common for someone to write a line of code, and not even realize that it happened to call for 100 or 200 new/deletes! This has caused a backlash, where C++ developer will fanatically nitpick at news and deletes, out of fear that they are being called accidentally all over the place.

These are, of course, generalizations. In no way do the entire C and C++ communities fit these molds. However, if you are getting flack over using new instead of putting things on the heap, this may be the root cause.

Cort Ammon
  • 10,221
  • 31
  • 45
0

It is rather strange that you see it so often. You must have been looking as some "lazy" code.

In C the technique that you describe is typically reserved to "opaque" library types, i.e. struct types whose definitions are intentionally made invisible to the client's code. Since the client cannot declare such objects, the idiom has to really on dynamic allocation in the "hidden" library code.

When hiding the definition of the struct is not required, a typical C idiom usually looks as follows

typedef struct CClass
{
    int member_a;
    float member_b;
} CClass;

CClass* CClass_init(CClass* cclass);
void CClass_release(CClass* cclass);

Function CClass_init initializes the *cclass object and returns the same pointer as result. I.e. the burden of allocating memory for the object is placed on the caller and the caller can allocate it in any way it sees fit

CClass cclass;
CClass_init(&cclass);
...
CClass_release(&cclass);

A classic example of this idiom would be pthread_mutex_t with pthread_mutex_init and pthread_mutex_destroy.

Meanwhile, using the former technique for non-opaque types (as in your original code) is generally a questionable practice. It is exactly questionable as gratuitous use of dynamic memory in C++. It works, but again, using dynamic memory when it is not required is as frowned upon in C as it is in C++.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765