The main problem to deal with is alignment. The pointer to the memory block returned by malloc
is correctly aligned for any object type, but the pointer just past the end of the size
member of your type may not be correctly aligned for some object types. You can work around that by using a union between the size
member and some other object of type max_align_t
:
typedef union {
size_t size;
max_align_t reserved__;
} my_data_header;
Then to allocate an object, add on the size of my_data_header
, and use a pointer just past the end of my_data_header
to get to point to your object:
my_data_header *head = malloc(sizeof(*head) + object_size);
if (head) {
head->size = object_size;
object_ptr = (void *)&head[1];
} else {
object_ptr = NULL;
}
However to free the memory, you need to free the head, not the object pointer. Alternatively, the pointer to the head can be retrieved from the object pointer:
if (object_ptr) {
head = &((my_data_head *)(void *)object_ptr)[-1];
} else {
head = NULL;
}
A set of interface functions could be defined to allow objects of any size to be allocated and freed and the current size to be retrieved:
obj_alloc.c
#include "obj_alloc.h"
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
typedef union {
size_t size;
max_align_t reserved__;
} obj_hdr;
static obj_hdr *obj_to_hdr(void *obj)
{
if (!obj) {
return NULL;
}
return (obj_hdr *)obj - 1;
}
size_t obj_size(const void *obj)
{
if (!obj) {
return 0;
}
return obj_to_hdr((void *)obj)->size;
}
void *obj_reallocarray(void *obj, size_t nmemb, size_t size)
{
obj_hdr *hdr = obj_to_hdr(obj);
if (!(nmemb && size)) {
/* New size is zero. */
free(hdr);
return NULL;
}
if ((SIZE_MAX - sizeof(*hdr)) / nmemb < size) {
/* Too big */
errno = ENOMEM;
return NULL;
}
hdr = realloc(hdr, sizeof(*hdr) + nmemb * size);
if (!hdr) {
/* Allocation failure. */
errno = ENOMEM;
return NULL;
}
/* Record size and return pointer to object. */
hdr->size = nmemb * size;
return hdr + 1;
}
void *obj_allocarray(size_t nmemb, size_t size)
{
return obj_reallocarray(NULL, nmemb, size);
}
void *obj_calloc(size_t nmemb, size_t size)
{
void *obj = obj_reallocarray(NULL, nmemb, size);
if (obj) {
memset(obj, 0, nmemb * size);
}
return obj;
}
void *obj_malloc(size_t size)
{
return obj_reallocarray(NULL, 1, size);
}
void *obj_realloc(void *obj, size_t size)
{
return obj_reallocarray(obj, 1, size);
}
void obj_free(void *obj)
{
free(obj_to_hdr(obj));
}
obj_alloc.h
#ifndef OBJ_ALLOC_H__INCLUDED
#define OBJ_ALLOC_H__INCLUDED
#include <stddef.h>
/**
* \brief Get current size of object.
*
* Get current size of object allocated by obj_malloc(),
* obj_realloc(), obj_calloc(), obj_allocarray(), or
* obj_reallocarray().
*
* \param[in] obj
* Pointer to object, or null pointer.
*
* \return current size of object.
*/
size_t obj_size(const void *obj);
/**
* \brief Free object.
*
* Free object allocated by obj_malloc(), obj_realloc(),
* obj_calloc(), obj_allocarray(), or obj_reallocarray().
*
* \param[in] obj
* Pointer to object, or null pointer.
*/
void obj_free(void *obj);
/**
* \brief Allocate an object from dynamic memory.
*
* Allocate an object of specified size from dynamic memory and
* record its size.
*
* \param[in] size
* Size of object to be allocated.
*
* \return On success, returns a pointer to the allocated object,
* or a null pointer if \p size is 0. On failure, returns a null
* pointer and sets \c errno to \c ENOMEM.
*/
void *obj_malloc(size_t size);
/**
* \brief Reallocate an object from dynamic memory.
*
* Reallocate an existing object to a new, specified size from
* dynamic memory and record its new size.
*
* obj_realloc(NULL, size) behaves like obj_malloc(size).
*
* obj_realloc(obj, 0) behaves like obj_free(obj) and returns a
* null pointer.
*
* If a pointer to an object is given, it must have been returned
* by a previous call to obj_malloc(), obj_realloc(),
* obj_calloc(), obj_allocarray(), or obj_reallocarray().
*
* \param[in] obj
* Pointer to existing object, or a null pointer.
* \param[in] size
* New size of object to be reallocated.
*
* \return On success, returns a pointer to the reallocated
* object, or a null pointer if \p size is 0. On failure, returns
* a null pointer and sets \c errno to \c ENOMEM, but the original
* object remains unchanged.
*/
void *obj_realloc(void *obj, size_t size);
/**
* \brief Allocate a zeroed array of objects from dynamic memory.
*
* Allocate memory for an array of objects from from dynamic
* memory and with a specified number of elements and a specified
* element size, recording the total size, and setting the memory
* contents to zero.
*
* obj_calloc(nmemb, size) is equivalent to
* obj_malloc(nmemb * size) followed by
* memset(ptr, 0, nmemb * size) if the return value is non-null.
* The call is valid even if the multiplication of \p nmemb by
* \p size would result in arithmetic overflow, but the function
* will fail to allocate memory in that case.
*
* \param[in] nmemb
* Number of elements to allocate.
* \param[in] size
* Size of each element.
*
* \return On success, returns a pointer to the allocated memory,
* or a null pointer if \p size is 0 or \p nmemb is 0. On failure,
* returns a null pointer and sets \c errno to \c ENOMEM.
*/
void *obj_calloc(size_t nmenb, size_t size);
/**
* \brief Allocate an array of objects from dynamic memory.
*
* Allocate memory for an array of objects from from dynamic
* memory and with a specified number of elements and a specified
* element size, recording the total size.
*
* obj_allocarray(nmemb, size) is equivalent to
* obj_malloc(nmemb * size). The call is valid even if the
* multiplication of \p nmemb by \p size would result in
* arithmetic overflow, but the function will fail to allocate
* memory in that case.
*
* \param[in] nmemb
* Number of elements to allocate.
* \param[in] size
* Size of each element.
*
* \return On success, returns a pointer to the allocated memory,
* or a null pointer if \p size is 0 or \p nmemb is 0. On failure,
* returns a null pointer and sets \c errno to \c ENOMEM.
*/
void *obj_allocarray(size_t nmemb, size_t size);
/**
* \brief Reallocate an array of objects from dynamic memory.
*
* Reallocate an existing object to an array of objects from
* dynamic memory with a specified number of elements and a
* specified element size, recording the new total size.
*
* obj_reallocarray(obj, nmemb, size) is equivalent to
* obj_realloc(obj, nmemb * size). The call is valid even if the
* multiplication of \p nmemb by \p size would result in
* arithmetic overflow, but the function will fail to reallocate
* memory in that case.
*
* obj_realloc(obj, 0, size) and obj_realloc(obj, nmemb, 0)
* behave like obj_free(obj) and return a null pointer.
*
* If a pointer to an object is given, it must have been returned
* by a previous call to obj_malloc(), obj_realloc(),
* obj_calloc(), obj_allocarray(), or obj_reallocarray().
*
* \param[in] obj
* Pointer to existing object, or a null pointer.
* \param[in] nmemb
* Number of elements to allocate.
* \param[in] size
* Size of each element.
*
* \return On success, returns a pointer to the reallocated
* memory, or a null pointer if \p nmemb is 0 or \p size is 0.
* On failure, returns a null pointer and sets \c errno to \c
* ENOMEM, but the original object remains unchanged.
*/
void *obj_reallocarray(void *obj, size_t n_memb, size_t size);
#endif