"Safe" memory management
are these functions in bad practice?


I would say not, as long as the original functions they are wrapping have the same function signature to allow their replacement.


  is there anything particularly wrong with the code I have written?


Allocating 0 bytes may return NULL, which does not indicate Out-of-memory.
Same for calloc(), realloc().

void* memory = malloc(size);
// if(memory == NULL){
if(memory == NULL &amp;&amp; size &gt; 0){


No point in calling free(memory) after if(memory == NULL)

if(memory == NULL){
  //  free(memory);


With eprintf(...), consider passing __FUNC__, __LINE__ of the error source to aid in debugging.  Or at least create unique error messages.

void* safe_malloc(size_t size){
   runtime_error("failed to malloc memory.");

void* safe_calloc(size_t num, size_t size){
    runtime_error("failed to calloc memory.");


In a couple places, use const to allow wider application of the functions that are not changing the referenced data.

// void make_error(char* type, char* message){
void make_error(const char* type, const char* message){


I am not a fan of your name-space choices for error.h which includes eprintf,
 STATUS_OKAY, STATUS_GENERIC_ERROR, make_error, ...  If I say these used in other ..c files how would I be guided back to error.h/error.c as their origin?  Although a bit verbose, I prefer error_printf, ERROR_STATUS_GENERIC,...

Minor: #include &lt;stdio.h&gt; need not be in safe_mem.h nor #include &lt;stdlib.h&gt; in error.h.  error.c should have #include &lt;stdlib.h&gt; as it calls exit().  error.c should not rely on error.h including that.  Similar issues for safe_mem.c



From a design standpoint, I would also include void safe_free(void *) as the functionality of these safe functions may expand and require that even if it present does nothing.  It makes safe_...() symmetric.