Handing initialization failures

  • Thread starter МакÑим Фомин
  • Start date
Ð

МакÑим Фомин

Hello.

I have a question regarding following problem. Let there is

struct Container{
void *buffer;
resource1 *res1;
resource2 *res2;
....
resource_n *res_n;
};

pointer to which is return by function like container_alloc().
Initialization of each member may fail and if last fails, and I need
to free() all other initialized resources (otherwise there would be
memory leakage). The situation become worse when any random "sub-
resource" initialization may fail.

What is best way to deal whis this? There is obvious solution to free
all previous allocated resources within "if" test block of each
element, however I am looking for a better solution.
 
J

James Kuyper

Hello.

I have a question regarding following problem. Let there is

struct Container{
void *buffer;
resource1 *res1;
resource2 *res2;
...
resource_n *res_n;
};

pointer to which is return by function like container_alloc().
Initialization of each member may fail and if last fails, and I need
to free() all other initialized resources (otherwise there would be
memory leakage). The situation become worse when any random "sub-
resource" initialization may fail.

What is best way to deal whis this? There is obvious solution to free
all previous allocated resources within "if" test block of each
element, however I am looking for a better solution.

Set all of the pointers to null at start.
If any allocation fails, free ALL of the pointers.
Situations like this are part of the reason why the standard allows null
pointers to be passed to free().
 
B

Ben Bacarisse

МакÑим Фомин said:
I have a question regarding following problem. Let there is

struct Container{
void *buffer;
resource1 *res1;
resource2 *res2;
...
resource_n *res_n;
};

pointer to which is return by function like container_alloc().
Initialization of each member may fail and if last fails, and I need
to free() all other initialized resources (otherwise there would be
memory leakage). The situation become worse when any random "sub-
resource" initialization may fail.

What is best way to deal whis this? There is obvious solution to free
all previous allocated resources within "if" test block of each
element, however I am looking for a better solution.

One solution is to initialise the struct first so that all the pointers
are null. Then you can safely free everything because free(0) is a
no-op. One way to do this is to keep a

static struct Container zero;

so that you can do

struct Container *sp = malloc(sizeof *sp);
if (sp != NULL) {
*sp = zero;
if ((sp->res1 = malloc(sizeof *sp->res1)) == NULL)
goto clean_up;
/* etc. */
}

If the 'res' members were in an array you would not need to do this --
the code is often simpler with arrays:

for (i = 0; i < n; i++)
if ((sp->res = malloc(sizeof *sp->res)) == NULL) {
for (j = 0; j < i; j++)
free(sp->res[j]);
break;
}
 
I

ImpalerCore

Hello.

I have a question regarding following problem. Let there is

struct Container{
void *buffer;
resource1 *res1;
resource2 *res2;
...
resource_n *res_n;

};

pointer to which is return by function like container_alloc().
Initialization of each member may fail and if last fails, and I need
to free() all other initialized resources (otherwise there would be
memory leakage). The situation become worse when any random "sub-
resource" initialization may fail.

What is best way to deal whis this? There is obvious solution to free
all previous allocated resources within "if" test block of each
element, however I am looking for a better solution.

Here is a possible way to deal with it.

\code untested
struct Container* container_alloc( size_of_buffer, r1, ..., rn )
{
struct Container* new_container = NULL;
bool alloc_failed = false;

new_container = malloc( sizeof (struct Container) );
if ( new_container )
{
new_container->buffer = !alloc_failed ?
malloc( size_of_buffer ) : NULL;
alloc_failed = new_container->buffer == NULL;

new_container->res1 = !alloc_failed ?
malloc( size_of_resource1 ) : NULL;
alloc_failed = new_container->res1 == NULL;

new_container->res2 = !alloc_failed ?
malloc( size_of_resource2 ) : NULL;
alloc_failed = new_container->res2 == NULL;

...

new_container->resN = !alloc_failed ?
malloc( size_of_resourceN ) : NULL;
alloc_failed = new_container->resN == NULL;

if ( alloc_failed )
{
free( new_container->buffer );
free( new_container->res1 );
free( new_container->res2 );
...
free( new_container->resN );
free( new_container );
new_container = NULL;
}
}

return new_container;
}
\endcode

The 'free' function allows freeing NULL pointers, so if an allocation
fault occurs, it should clean any partial resources that's been
allocated. Be sure to test it, as it's just off the top of my head.

Best regards,
John D.
 
J

jacob navia

Le 16/09/11 20:42, МакÑим Фомин a écrit :
Hello.

I have a question regarding following problem. Let there is

struct Container{
void *buffer;
resource1 *res1;
resource2 *res2;
...
resource_n *res_n;
};

pointer to which is return by function like container_alloc().
Initialization of each member may fail and if last fails, and I need
to free() all other initialized resources (otherwise there would be
memory leakage). The situation become worse when any random "sub-
resource" initialization may fail.

What is best way to deal whis this? There is obvious solution to free
all previous allocated resources within "if" test block of each
element, however I am looking for a better solution.

Besides the other answers you got consider using a garbage collector.
All these problems go away with that solution. (Others may appear).

Under windows you can use the gc dll with any compiler, as in linux.
 
I

Ian Collins

Le 16/09/11 20:42, МакÑим Фомин a écrit :

Besides the other answers you got consider using a garbage collector.
All these problems go away with that solution. (Others may appear).

Only if the resource in question is memory.
 
M

Malcolm McLean

Hello.

I have a question regarding following problem. Let there is

struct Container{
void *buffer;
resource1 *res1;
resource2 *res2;
...
resource_n *res_n;

};

pointer to which is return by function like container_alloc().
Initialization of each member may fail and if last fails, and I need
to free() all other initialized resources (otherwise there would be
memory leakage). The situation become worse when any random "sub-
resource" initialization may fail.

What is best way to deal whis this? There is obvious solution to free
all previous allocated resources within "if" test block of each
element, however I am looking for a better solution.
I just write

res1 = allocateresource1();
if(res1 == NULL)
goto outofmemory;

....
outofmemory:
cleanupeverything;
return NULL;

You sometimes have to be careful to set all the pointers to NULL, so
that they can be either passed to free harmlessly or detected as
unallocated and not freed.
 
Ð

МакÑим Фомин

Set all of the pointers to null at start.
If any allocation fails, free ALL of the pointers.
Situations like this are part of the reason why the standard allows null
pointers to be passed to free().

Actually that resources are not dynamic memory, they are allocated in
the way struct Container does, but I can make allocation functions
behave like free(NULL) does.
 
P

Paul N

Hello.

I have a question regarding following problem. Let there is

struct Container{
void *buffer;
resource1 *res1;
resource2 *res2;
...
resource_n *res_n;

};

pointer to which is return by function like container_alloc().
Initialization of each member may fail and if last fails, and I need
to free() all other initialized resources (otherwise there would be
memory leakage). The situation become worse when any random "sub-
resource" initialization may fail.

What is best way to deal whis this? There is obvious solution to free
all previous allocated resources within "if" test block of each
element, however I am looking for a better solution.

One possible approach - no-one else has mentioned it, perhaps that
suggests it's no good - would be to have nested ifs. For instance,
something like:

struct Container *container_alloc(void) {
// allocate container c itself
c -> res1 = whatever
if (worked) {
c -> res2 = whatever
if (worked) {
return c; }
free res2 }
free res1
return NULL;
}
 
N

Nobody

What is best way to deal whis this?

C++ and RAII ;)
There is obvious solution to free all
previous allocated resources within "if" test block of each element,
however I am looking for a better solution.

One solution not mentioned so far is:

obj = obj_alloc();
if (!obj)
goto fail0;
obj->res1 = res1_alloc();
if (!obj->res1)
goto fail1;
obj->res2 = res2_alloc();
if (!obj->res2)
goto fail2;
...
return obj; /* success */
...
fail3:
res2_free(obj->res2);
fail2:
res1_free(obj->res1);
fail1:
obj_free(obj);
fail0:
return NULL;

Personally, I'd just go with having all of the _free() functions ignore
NULL pointers, so that you can legitimately call the top-level obj_free()
function on a partially-constructed object.
 
J

James Kuyper

One possible approach - no-one else has mentioned it, perhaps that
suggests it's no good - would be to have nested ifs. For instance,
something like:

struct Container *container_alloc(void) {
// allocate container c itself
c -> res1 = whatever
if (worked) {
c -> res2 = whatever
if (worked) {
return c; }
free res2 }
free res1
return NULL;
}

I believe that this is basically the "obvious solution" that he was
referring to. The problem with that solution is that it gets very
unwieldy when the list of resources to be allocated gets long.
 
L

Lauri Alanko

struct Container{
void *buffer;
resource1 *res1;
resource2 *res2;
...
resource_n *res_n;
};

pointer to which is return by function like container_alloc().
Initialization of each member may fail and if last fails, and I need
to free() all other initialized resources (otherwise there would be
memory leakage). The situation become worse when any random "sub-
resource" initialization may fail.

What is best way to deal whis this?

I have found the following approach relatively neat, and I think it
demonstrates an acceptable use of goto:

Container* container_new(void)
{
Container* c = container_alloc();
if (!c) goto fail_c;

c->res1 = resoure1_new();
if (!c->res1) goto fail_res1;

c->res2 = resource2_new();
if (!c->res2) goto fail_res2;

...

c->res_n = resource_n_new();
if (!c->res_n) goto fail_res_n;

return c;

fail_res_n:
...
resource2_destroy(c->res2);
fail_res2:
resource1_destroy(c->res1);
fail_res1:
container_dealloc(c);
fail_c:
return NULL;
}

The good thing with this approach is that the code is quite clean and
all error handling is explicit. On the other hand, the error handling
may then duplicate some code with a container_destroy function that
would free a Container and its contents once they were all
successfully allocated. I think this is a reasonable tradeoff, though.


Lauri
 
G

Gene

I have found the following approach relatively neat, and I think it
demonstrates an acceptable use of goto:

Container* container_new(void)
{
        Container* c = container_alloc();
        if (!c) goto fail_c;

        c->res1 = resoure1_new();
        if (!c->res1) goto fail_res1;

        c->res2 = resource2_new();
        if (!c->res2) goto fail_res2;

        ...

        c->res_n = resource_n_new();
        if (!c->res_n) goto fail_res_n;

        return c;

fail_res_n:
        ...
        resource2_destroy(c->res2);
fail_res2:
        resource1_destroy(c->res1);
fail_res1:
        container_dealloc(c);
fail_c:
        return NULL;

}

The good thing with this approach is that the code is quite clean and
all error handling is explicit. On the other hand, the error handling
may then duplicate some code with a container_destroy function that
would free a Container and its contents once they were all
successfully allocated. I think this is a reasonable tradeoff, though.

Lauri

I've used this idea and it works fine in a straight-line procedure,
but doesn't hold up if allocation ends up being conditional. At that
point it just falls apart.

Over years I've fallen into this strategy:

{
Declarations
Initializers: Put each data structure in a known state where no
resources have been allocated.

Setup: Assign resources data structures. If any part of an
allocation fails, return it to the initialized state. Allocators can
(and often should) be called lazily: just before the respective
structure is used.

Uses: Use the data structures. The uses can employ an
is_allocated() predicate for each data structure to decide if they can
run successfully. Any kind of abort does a goto cleanup;

cleanup:
Clearers: Return each data structure to its initialized state.
}

The secret to happiness is the invariants: (1) init_ is always called
exactly once just after declaration. (2) Setup is idempotent. You
can call it repeatedly, potentially with different parameters, and
it's no harm no foul. (3) Cleanup is also idempotent. If a previous
setup_ succeeded, it releases held resources and returns all to the
init_ state. Additional calls do nothing.

If you were implementing a vector data type in this scheme, it would
be:

typedef struct float_vector_s {
unsigned n_elts;
float *elts;
} FLOAT_VECTOR;

void init_float_vector(FLOAT_VECTOR *v)
{
v->n_elts = ~0u; // "initialized not allocated"
v->elts = NULL;
}

void clear_float_vector(FLOAT_VECTOR *v)
{
free(v->elts);
// clear_ (almost) always ends with init_
init_float_vector(v);
}

void setup_float_vector(FLOAT_VECTOR *v, unsigned n_elts)
{
// Setup (almost) always clears first.
clear_float_vector(v);
if (n_elts > 0) {
v->elts = malloc(n_elts * sizeof *v->elts);
if (v->elts) v->n_elts = n_elts;
}
else v->n_elts = 0; // 0-vectors are okay...
}

int is_float_vector_setup_ok(FLOAT_VECTOR *v)
{
return v->n_elts != ~0u;
}

Now in use it looks like this:

FLOAT_VECTOR v[1]; // 1-element array saves &'s.
init_float_vector(v); // just once!

// stuff

if (I need a vector) {
setup_float_vector(v, 100);

// We can test success.
if (is_float_vector_setup_ok(v)) {

// oops need more space. No problem.
setup_float_vector(v, 200);

// abort if space couldn't be found
if (!is_float_vector_setup_ok(v)) go cleanup;
}

// something might go wrong. No problem...
if (bad stuff happens) goto cleanup;

// If you need the space back here, just clear it
clear_float_vector(v);

// something else might go wrong. It's no problem
// that v is already clear.
if (bad stuff happens) goto cleanup;
}

cleanup:
clear_float_vector(v); // Idempotent, so an extra call doesn't
matter.
... and all the other clear_ routines are called here.


Note that this just generalizes the free(NULL) mechanism James Kuyper
mentioned.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,756
Messages
2,569,540
Members
45,025
Latest member
KetoRushACVFitness

Latest Threads

Top