Home > Blockchain >  What is the typical prototype for a deallocation function in C?
What is the typical prototype for a deallocation function in C?

Time:01-30

Looking at code on examples on StackOverflow I've noticed two distinct prototypes for object deallocation:

struct foo *foo_create(int);
void foo_free_v1(struct foo *);
void foo_free_v2(struct foo **);

void bar() {

    struct *foo = foo_create(7);

    // ...

    // Version 1
    foo_free_v1(foo);

    // Version 2
    foo_free_v2(&foo);
}

The standard library function free uses the first form.

Is the second form idiomatic in C? If yes, for what purpose?

CodePudding user response:

The ones that take a pointer-to-pointer do so because they have the added convenience of automatically nulling out your variable for you.

They might look like:

void foo_free_v1(struct foo *f) {
    free(f->a);
    free(f->b);
    free(f->c);
    free(f);
}
void foo_free_v2(struct foo **f) {
    if (*f == NULL) return; // This has been freed before, don't do it again!

    free((*f)->a);
    free((*f)->b);
    free((*f)->c);
    free(*f);

    *f = NULL; // Null out the variable so it can't be freed again.
}

This attempts to protect against double-free bugs. How good of an idea that, is debatable. See the comment thread below.

CodePudding user response:

In the first function

void foo_free_v1(struct foo *);

the original pointer used as an argument expression is passed to the function by value. It means that the function deals with a copy of the value of the pointer passed to the function.

Changing the copy does not influence on the original pointer used as an argument expression.

Consider for example the function definition

void foo_free_v1(struct foo *p)
{
    free( p );
    p = NULL;
}

This statement within the function

p = NULL;

does not change the original pointer to NULL. It sets to NULL the function local variable p. As a result the pointer used as an argument expression after calling the function will have an invalid value. That is a value that does not point to an existing object.

In the second function declared like that

void foo_free_v2(struct foo **);

the original pointer used as an argument expression is accepted by reference through a pointer to it. So dereferencing the pointer you have a direct access to the original pointer used as an argument expression.

Consider a possible function definition

void foo_free_v2(struct foo **p)
{
    free( *p );
    *p = NULL;
}

In this case in the statement

*p = NULL;

it is the original pointer that is set to NULL. As a result the original pointer does not have an invalid value.

Consider for example a sungly-linked list like

struct SinglyLinkedList
{
    int data;
     SinglyLinkedList *next;
};

In main you can declare the list like

struct SinglyLinkedList *head = NULL;

Then you can add new nodes to the list. As the pointer head is initialized then the function that adds new nodes will work correctly.

After that you can destroy the list.

If you will call the first function declared like

void foo_free_v1(struct SinglyLinkedList *head );

then after calling the function the pointer head declared in main will have an invalid value. That is there will be an inconsistency. The list does not have already elements but its pointer to the head ode is not equal to NULL.

Is you call the function declared lik

void foo_free_v1(struct SinglyLinkedList **head );

then indeed the pointer to the head node in main will be equal to NULL that indeed means that the list is empty. And if you have a function that checks whether a list is empty you can pass the pointer to the function without producing undefined behavior.

  • Related