I have never been entirely comfortable using malloc()
and free()
directly. For one thing, 99% of the time that I would like to call malloc
, I would prefer it to take two arguments like calloc
and do the multiplication itself, instead of taking one argument. Beyond that, I've often wished that free
could magically zero out the pointer it is passed (which, of course, is impossible for it to do, since its pointer argument is pass-by-value).
So I've put together a very small library of wrapper functions to address these and minor other issues I have with malloc
/ calloc
/ realloc
/ free
/ memset
/ memcpy
. The functions in this little library are:
mem_alloc
— Allocate a block of memory, given an item count and item size. This is simply amalloc
wrapper.mem_alloc_clear
— Allocate and clear a block of memory, given an item count and item size. This is simply acalloc
wrapper.mem_clear
— Clear a block of memory, given a pointer, item count, and item size. This is amemset
/bzero
wrapper.mem_copy
— Copy a block of memory, given a source and destination pointer, item count, and item size. This is amemcpy
wrapper.mem_realloc
— Reallocate a block of memory, given a pointer, item count, and item size. This is simply arealloc
wrapper.mem_alloc_copy
— Allocate a block of memory and copy memory into it, given a source pointer, item count, and item size.mem_alloc_clone
— Allocate a block of memory and copy memory into it, given only a source pointer. The size of the block is automagically determined by callingmem_size
.mem_dealloc
— Deallocate a block of memory, given a pointer, and set that pointer to null. This is afree
wrapper, implemented as a hidden function and a visible macro.mem_size
— Return the size of a previously allocated block of memory. This is simply amalloc_size
wrapper.
Note that none of these take a size_t
of bytes directly. Instead, they compute the size by multiplying a count of items times the size of an item (like calloc
does). This is much more convenient for me and saves me a whole bunch of parenthesization in client code.
And now here is the source code...
Header file
extern size_t mem_size (void *memory);
extern void *mem_alloc (size_t item_count, size_t item_size);
extern void *mem_alloc_clear (size_t item_count, size_t item_size);
extern void mem_clear (void *memory, size_t item_count, size_t item_size);
extern void *mem_realloc (void *memory, size_t item_count, size_t item_size);
extern void *mem_alloc_copy (void *memory, size_t item_count, size_t item_size);
extern void *mem_alloc_clone (void *memory);
extern void mem_copy (void *memory_dest, void *memory_src, size_t item_count, size_t item_size);
extern void mem_dealloc_ (void **memory); // (Do not call directly)
#define mem_dealloc(p) mem_dealloc_((void **)(void *)(p))
Notes:
The actual deallocation routine, named
mem_dealloc_
, should not be called directly, as it is wrapped by a macro namedmem_dealloc
, which allows the caller to writemem_dealloc(&pointer)
without the compiler warning about incompatible pointer types and without having to explicitly writemem_dealloc((void **)&pointer)
.The reason for writing
(void **)(void *)(p)
instead of(void **)(p)
in themem_dealloc
macro is because the latter causes compile-time errors with ARC (Automatic Reference Counting) in cases of memory pointing to Objective-C objects, whereas the former has no such issue.
Implementation file
Return allocation size
mem_size
returns the size in bytes of a previously-allocated memory block. It relies on the built-in function malloc_size
.
size_t mem_size(void *memory)
{
if (memory != NULL)
return malloc_size(memory);
else
return 0;
}
Allocate memory
mem_alloc
allocates a new block of memory, given an item count and item size. Upon failure, like malloc
, it returns NULL
.
void *mem_alloc(size_t item_count, size_t item_size)
{
void *memory = malloc(item_count * item_size);
return memory;
}
Allocate and clear memory
mem_alloc_clear
allocates and clears a new block of memory (technically, it allocates a block of cleared memory), given an item count and item size. Upon failure, like calloc
, it returns NULL
.
void *mem_alloc_clear(size_t item_count, size_t item_size)
{
void *memory = calloc(item_count, item_size);
return memory;
}
Clear memory
mem_clear
clears all bytes in a block of memory to zero, given a block pointer, an item count, and item size.
void mem_clear(void *memory, size_t item_count, size_t item_size)
{
assert(memory != NULL);
memset(memory, 0, item_count * item_size);
}
Copy memory
mem_copy
copies a block of memory, given a source and destination pointer (in traditional C reverse order), an item count, and item size.
void mem_copy(void *memory_dest, void *memory_src, size_t item_count, size_t item_size)
{
assert(memory_dest != NULL);
assert(memory_src != NULL);
memcpy(memory_dest, memory_src, item_count * item_size);
}
Reallocate memory
mem_realloc
reallocates a block of memory, given a pointer to the existing block and a new item count and item size. Upon failure, like realloc
, it returns NULL
.
void *mem_realloc(void *memory, size_t item_count, size_t item_size)
{
memory = realloc(memory, item_count * item_size);
return memory;
}
Allocate and copy memory
mem_alloc_copy
allocates a new block of memory and copies the contents of an existing area of memory, given a pointer to the existing area, an item count, and item size. Upon failure, it returns NULL
.
void *mem_alloc_copy(void *memory, size_t item_count, size_t item_size)
{
assert(memory != NULL);
void *new_memory = mem_alloc(item_count, item_size);
if (new_memory != NULL)
mem_copy(new_memory, memory, item_count, item_size);
return new_memory;
}
Allocate cloned memory
mem_alloc_clone
allocates a new block of memory, cloning an existing block of memory into it. To determine the size of the block, it relies upon the mem_size
function. Upon failure, it returns NULL
.
void *mem_alloc_clone(void *memory)
{
assert(memory != NULL);
return mem_alloc_copy(memory, mem_size(memory), 1);
}
Deallocate memory
mem_dealloc_
deallocates a block of memory, given a pointer to it. This routine should not be called directly, but only via the mem_dealloc
macro, which is defined in the header file.
mem_dealloc
deallocates a block of memory, given a pointer to a pointer to it, and then sets the pointer to NULL
.
The calling convention here is to write mem_dealloc(&pointer)
.
void mem_dealloc_(void **memory)
{
assert(memory != NULL);
if (*memory)
{
free(*memory);
*memory = NULL;
}
}
memmove()
? \$\endgroup\$memmove()
. I've never really needed it in 25 years of C programming, except maybe once long ago when shifting some strings around the hard way. \$\endgroup\$restrict
in mem_copy. \$\endgroup\$