6

The problem

I have this structure that I want to create a "constructor" for it.

struct example {
    int x, y, z; /* various members */
    struct another *another; /* pointer to another structure */
}

The two different ways I know of

  1. Using functions that create and delete structures on heap

I can create structures by making functions that allocates them on the heap like this:

/* create example */
struct example *example_new(int x, int y, int z) {
    struct example *p = malloc(sizeof *p);
    p->x = x;
    p->y = y;
    p->z = z;
    p->another = another_new();
}

/* delete example */
void example_new(struct example *p) {
    another_del(p->another);
    free(p);
}
  1. Using functions that initializes and frees memory for structure through pointer

Or I could initalize the structure by passing pointer to the function and have the user responsible for allocating and deallocating memory for it.

/* initalize example */
void example_init(struct example *p, int x, int y, int z) {
    assert(p);
    p->x = x;
    p->y = y;
    p->z = z;
    p->another = another_new();
}

/* free memory allocated for example */
void example_free(struct example *p) {
    another_del(p->another);
}

My thoughts

I usually prefer first approach when creating recursive structures (like Trees and Linked Lists) but use the second approach in all other cases.

I tried to use the second approach for a recursive structure and it turned out to be quite messy.

The question

How do you choose between these two ways? Is there a third way you would use to solve this problem?

  • 3
    Switch to C++? :) Honestly, though, as such this kind of seems like a solved problem... – Lightness Races with Monica Jan 7 '16 at 18:38
4

There is no answer that's always going to be the right one, because different programs will have different needs. If you have enough information to make an informed call one way or the other, that's the way you should go. (For example, if your program is required to have the highest throughput possible, forcing a malloc()/free() pair each time some function needs to use your structure probably isn't going to fly.)

If you're going to build a general-purpose library, it isn't much additional effort to build it in a way that supports both:

void example_init(struct example *p, int x, int y, int z) - Initializes the structure pointed at by p. This can be called by anyone who has a struct example whether it's an automatic, allocated on the heap or pulled out of a pool.

void example_destroy(struct example *p) - Does whatever is necessary to de-initialize the structure pointed at by p. (In your case, this would be freeing the memory allocated for the another member.) Note that it does not free p, because it has no knowledge of whether or not the structure is on the heap or if the caller isn't going to re-use it by calling example_init() again.

struct example * example_new(int x, int y, int z) - Allocates space for the structure on the heap, calls example_init() against it and returns the pointer.

void example_del(struct example * p) - Calls example_destroy(p) and then frees p.

There is a hazard to this approach, which is that someone could try to do pairs of example_init()/example_del() or example_new()/example_destroy() calls. Tools like valgrind and some implementations of free() can weed those problems out automagically. Another approach would be to set aside a bit somewhere in the structure that can be set when the structure was on the heap and use assertions to complain if someone goes about it the wrong way. (example_del() would need to clear the bit before calling example_destroy().)

3

The primary advantage of the first option is that it encapsulates the memory allocation into your "new" and "delete". You only have to remember to create and destroy your structures. With the second option, you have to remember separately to create/destroy and allocate/free your objects.

The advantage of the second option is that it allows you to create objects on the stack:

struct example;
example_init(&example, 1,2,3);

This can have big performance benefits over allocating on the heap. However, you have to be very careful to call example_free before your object goes out of scope.

So option 1 should generally be less error prone, and should be preferred unless your requirements mean you need the performance benefits of putting these objects on the stack.

1

The answer of which approach to use depends on the memory management strategy that users of your api will employ. For example, using an Object pool or Region based memory requires using strategy 2. If there is any chance that consumers of the api will want more control over memory management, strategy 2 is preferable.

If you are convinced that malloc and free are enough for all consumers of your api, strategy 1 is adequate. Even in this case it seems reasonable to provide the functions of 2 and to define the malloc and free versions in terms of those functions. This has the benefit of separating the logically distinct tasks of allocating memory and initializing that memory.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.