Allocating through a pointer to a pointer parameter

I have this macro:


/*
 * int  callocs(type **ptr, size_t nmemb);
 *
 * Safe & simple wrapper for `calloc()`.
 *
 * PARAMETERS:
 * ptr:     Memory will be allocated, and a pointer to it will be stored
 *          in *ptr.
 * nmemb:   Number of elements in the array.
 *
 * RETURN:
 *  0:          OK.
 *  != 0:       Failed.
 *
 * FEATURES:
 * - Safely computes the element size (second argument to `calloc()`).
 * - Returns non-zero on error.
 * - Doesn't cast.
 * - The pointer stored in `*ptr` is always a valid pointer or NULL.
 *
 * EXAMPLE:
 *      #define ALX_NO_PREFIX
 *      #include <libalx/base/stdlib/alloc/callocs.h>
 *
 *              int *arr;
 *
 *              if (callocs(&arr, 7))       // int arr[7];
 *                      goto err;
 *
 *              // `arr` has been succesfully allocated here
 *              free(arr);
 *      err:
 *              // No memory leaks
 */
#define callocs(ptr, nmemb) (                                           \
{                                                                       \
        __auto_type     ptr_    = (ptr);                                \
                                                                        \
        *ptr_   = calloc(nmemb, sizeof(**ptr_));                        \
                                                                        \
        !(*ptr_);                                                       \
}                                                                       \
)

and I would like it to be a function to improve safety. This would be the first idea:

#define callocs(ptr, nmemb) (                                           \
{                                                                       \
        __auto_type     ptr_    = (ptr);                                \
                                                                        \
        callocs__(ptr_, nmemb, sizeof(**ptr_));                         \
}                                                                       \
)


int     callocs__(void **ptr, ptrdiff_t nmemb, size_t size)
{

        if (nmemb < 0)
                goto ovf;

        *ptr    = calloc(nmemb, size);

        return  !*ptr;
ovf:
        errno   = ENOMEM;
        *ptr    = NULL;
        return  ENOMEM;
}

But then the compiler complains with:

error: passing argument 1 of callocs__ from incompatible pointer type [-Werror=incompatible-pointer-types]
note: in expansion of macro callocs
note: expected void ** but argument is of type struct My_Struct **

Is a simple explicit cast to (void **) safe?:

#define callocs(ptr, nmemb) (                                           \
{                                                                       \
        __auto_type     ptr_    = (ptr);                                \
                                                                        \
        callocs__((void **)ptr_, nmemb, sizeof(**ptr_));                \
}                                                                       \
)

By safe I mean both from the Standard point of view (which I guess is not) and from implementations point of view (in this specific case, GNU C) (which I'm not sure).

And if not, would an intermediate pointer of type void * be enough?:

#define callocs(ptr, nmemb) (                                           \
{                                                                       \
        __auto_type     ptr_    = (ptr);                                \
        void            *vp_;                                           \
        int             ret_;                                           \
                                                                        \
        ret_    = callocs__(&vp_, nmemb, sizeof(**ptr_))                \
        *ptr_   = vp_;                                                  \
        ret_;                                                           \
}                                                                       \
)

Is there any other solution?

Upvotes: 3

Views: 98

Answers (1)

chux
chux

Reputation: 153498

Is a simple explicit cast to (void **) safe?:
And if not, would an intermediate pointer of type void * be enough?:

The (void **) approach is reasonable, yet relies on some_other_type ** being compatible with void **. It falls under:

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer. C17dr § 6.3.2.2 7

So trouble in a rare case of a pointer to one type has a different alignment requirement than a pointer to a void *. I doubt such an implementation exists. To be clear, this is a difference of alignment requirement of pointer types, not the objects they point to.


Is there any other solution?

To avoid "passing argument 1 of callocs__ from incompatible pointer type" and to maintain type correctness, consider making the helper function callocs__() as

void *callocs__(ptrdiff_t nmemb, size_t size, int *error);
// ^------------return pointer to allocated data

Then

#define callocs(ptr, nmemb) (                             \
{                                                         \
        __auto_type     ptr_    = (ptr);                  \
        int error;                                        \
        *ptr = callocs__(nmemb, sizeof(**ptr_), &error);  \
        error;                                            \
}                                                         \
)

Note: I applaud the the overall objective of making a function to handle the subtle vagaries of *alloc().

Upvotes: 1

Related Questions