5
\$\begingroup\$

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 a malloc wrapper.
  • mem_alloc_clear — Allocate and clear a block of memory, given an item count and item size. This is simply a calloc wrapper.
  • mem_clear — Clear a block of memory, given a pointer, item count, and item size. This is a memset / bzero wrapper.
  • mem_copy — Copy a block of memory, given a source and destination pointer, item count, and item size. This is a memcpy wrapper.
  • mem_realloc — Reallocate a block of memory, given a pointer, item count, and item size. This is simply a realloc 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 calling mem_size.
  • mem_dealloc — Deallocate a block of memory, given a pointer, and set that pointer to null. This is a free 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 a malloc_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:

  1. The actual deallocation routine, named mem_dealloc_, should not be called directly, as it is wrapped by a macro named mem_dealloc, which allows the caller to write mem_dealloc(&pointer) without the compiler warning about incompatible pointer types and without having to explicitly write mem_dealloc((void **)&pointer).

  2. The reason for writing (void **)(void *)(p) instead of (void **)(p) in the mem_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;
   }
}
\$\endgroup\$
  • 1
    \$\begingroup\$ Is there any particular reason you've chosen not to also wrap memmove()? \$\endgroup\$ – This isn't my real name May 30 '13 at 1:22
  • \$\begingroup\$ Huh... no, actually, it didn't occur to me to add a wrapper for 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\$ – Todd Lehman May 30 '13 at 1:25
  • 1
    \$\begingroup\$ You might want to mark your pointers as restrict in mem_copy. \$\endgroup\$ – Sven Jun 20 '13 at 20:01
3
\$\begingroup\$

Your code looks good, but your library doesn't seem to do much. The thing that bothers you (from my understanding), is that you can't call malloc like calloc and that the pointer given to free doesn't get set to NULL.

IMHO, that doesn't need a whole library which basically only wraps functions. The following two macros would achive the same, but without the overhead:

#define mymalloc(item_count, item_size) malloc((item_count) * (item_size))
#define myfree(ptr) do { \
  free(ptr); \
  (ptr) = NULL; \
} while(0)

Why the whole library if the two macros above could do the same?

\$\endgroup\$
  • \$\begingroup\$ Thanks. Originally, I had a bunch of code involving pointer arithmetic that implemented the malloc_size functionality manually using a metadata struct tucked away at the head of every allocation block, so the wrappers were actually necessary then, as I need to track the sizes of allocated blocks in a cache class at a higher level and the best place to track the size is in the block itself. But then I discovered malloc_size and took all that out because it was unneeded. \$\endgroup\$ – Todd Lehman May 27 '13 at 19:15
  • \$\begingroup\$ Regarding library vs. macros — as it stands now, 7 of these 9 routines are basically direct wrappers for standard library functions. The other 2 wrap a combination of standard library functions. I like having them in a unified namespace mem_xxxxx. Not listed here are also some (more complex) wrappers for doing memory-mapping of files conveniently. These functions here, as you point out, could all be macros instead of functions, yeah...although I'd probably go with inline functions rather than macro definitions. \$\endgroup\$ – Todd Lehman May 27 '13 at 19:20
  • \$\begingroup\$ p.s. I like your do { xxx; yyy; } while (0) trick. Nice. \$\endgroup\$ – Todd Lehman May 27 '13 at 19:20
  • 2
    \$\begingroup\$ Note that the semi-colon after while(0) should be deleted \$\endgroup\$ – William Morris May 28 '13 at 15:45
2
\$\begingroup\$

I support the idea of this abstraction although I would make all of these methods static inline and drop them in a header file. With the state of optimizers today I think the overhead would be minimal.

I like the abstraction mostly because I deal with code that runs on all sorts of different embedded platforms. Some of which do not have the standard-C heap functions. It makes porting a breeze.

For the API itself I would merge the functionality of mem_alloc_copy() into mem_copy(), thusly:

void * mem_copy(void *memory_dest, void *memory_src, size_t item_count, size_t item_size)
{
    assert(memory_src != NULL);
    void * const result = (memory_dest != NULL) ? memory_dest : mem_alloc(item_count, item_size);
    if (result != NULL)
        mem_copy(result, memory_src, item_count, item_size);
    return result;
}

And mem_alloc_clone() I would rename to mem_dup() [like strdup()].

Also, your mem_copy() code snippet appears to calling itself using tail recursion, um, like forever. Care needs to be taken with mem_alloc_copy() and mem_copy() as they can cause memory access violations if item_count and item_size together exceed the original allocation size of the source pointer.

\$\endgroup\$
  • \$\begingroup\$ Fixed the mem_copy typo where it should have said memcpy — thanks. Just added that one yesterday and hadn't actually tested it yet (d'Oh!). \$\endgroup\$ – Todd Lehman May 29 '13 at 2:51
  • 1
    \$\begingroup\$ The reason I called it mem_alloc_clone is because its primary intended use is for inside a clone instance method of a class — e.g., when cloning the backing store for an object. \$\endgroup\$ – Todd Lehman May 29 '13 at 2:54
2
\$\begingroup\$

I agree with this answer that what you have written is not necessary. All the same, I have a few comments on the code, the main one being that malloc_size() is a non-standard function. Your Mac has it and perhaps the BSDs too, but Linux and Windows don't. This limits the usefulness of the library still further.

Some minor coding issues are the lack of braces, eg on conditionals, and unnecessary variables, as in:

         void *memory = malloc(item_count * item_size);
         return memory;

which could be just

         return malloc(item_count * item_size);

Also you don't use const where you could (for unchanged pointer parameters, e.g. memory_src in mem_copy).

In mem_alloc_clone the call to mem_alloc_copy has the memory parameter in the wrong place.

\$\endgroup\$
  • \$\begingroup\$ I had the intermediate variable names in there because I was stepping through with the interactive debugger once to track down a problem that had occurred when I'd accidentally passed a sizeof(uint64_t *) instead of sizeof(uint64_t). Also, at one time I had considered throwing an exception on null pointers, so I would have needed the intermediate variable for that. Yeah, they could be pruned out now. \$\endgroup\$ – Todd Lehman May 27 '13 at 19:27
  • \$\begingroup\$ p.s. Thanks for noticing the parameter misplacement. That was actually something I just introduced last night while posting this! (d'Oh) While describing the functions, I'd noticed that my parameter ordering wasn't consistent across all the functions, so I cleaned it up here after pasting in the code. Then I went back and changed the real code. But I goofed here when I made the change in parallel. \$\endgroup\$ – Todd Lehman May 27 '13 at 19:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.