Home > front end >  Can realloc be used to change a **pointer to a *pointer?
Can realloc be used to change a **pointer to a *pointer?

Time:06-12

This code is from Learn C The Hard Way by Zed Shaw. In exercise 34 one of the practice problems is to implement a dynamic array, which is just an array of pointers enclosed in a struct.

The struct looks like this:

typedef struct DArray {
    int end;
    int max;
    size_t element_size;
    size_t expand_rate;
    void **contents;
};

To initialize one of these we use DArray_create()

DArray *DArray_create(size_t element_size, size_t initial_max)
{
    DArray *array = malloc(sizeof(DArray));
    array->max = initial_max;

    array->contents = calloc(initial_max, sizeof(void *));

    array->end = 0;
    array->element_size = element_size;
    array->expand_rate = 300;

    return array;
}

There is a also a function to resize the array:

static inline int DArray_resize(DArray *array, size_t newsize)
{
    array->max = newsize;

    void *contents = realloc(array->contents, array->max * sizeof(void *));

    array->contents = contents;

    return 0;
}

There are also some memory checks in there that I left out.

My question has to do with the way calloc and realloc are used. The first calloc in DArray_create() assigns to the void **contents double pointer in the array struct, which makes sense to me because it is an array of void pointers.

The realloc in DArray_resize() then resizes the memory but uses a void *contents single pointer which then replaces the original double pointer. Why is this OK? I have tested DArray_resize() using both a single pointer and a double pointer and they both seem to work. If the original calloc created a void ** pointer, why is it OK for realloc to change it to a void * pointer?

CodePudding user response:

void * is a special kind of pointer. It can be converted to and from any other type of data pointer.

realloc accepts and returns void *. It doesn't know what kind of pointer your program operates on. The caller has to convert your pointer to void * upon calling realloc, and convert the result back fromvoid *. This conversion is so ubiquitous in C that it is done automatically.

Consider the original call to calloc.

array->contents = calloc(initial_max, sizeof(void *)); // <- returns void*
                                                       // gets converted to void**

calloc does not create a void ** pointer. It returns a void * pointer (just like realloc does). It is the caller's responsibility co convert it to whatever pointer type is needed. The conversion happens upon assignment, but if you want, you can insert an explicit cast:

array->contents = (void **)calloc(initial_max, sizeof(void *));

Returning to realloc, the same thing happens there, only this time an explicit intermediate variable is used:

void *contents = realloc(array->contents, array->max*sizeof(void*)); // void* returned here
array->contents = contents; // converted to void** here

You can shift the conversion to the line where realloc is called if you want. It doesn't really matter where it is done.

void **contents = realloc(array->contents, array->max*sizeof(void*)); // converted here
array->contents = contents; // assignment, no conversion

There is also an implicit conversion of array->contents to void* when realloc is called. You can also use a cast here if you want:

void **contents = (void**)realloc((void*)array->contents, array->max*sizeof(void*));

... or an intermediate variable, or both. It doesn't really add anything to the quality of the program though, so this is not normally done.

CodePudding user response:

It's legal because void * has some special rules. Any pointer of type T * can be converted to void* and then back again to T* and the result shall compare equal to the original pointer.

Also keep in mind that realloc returns void * so in void *contents = realloc(...) there is no conversion.

  • Related