You might consider using variadic functions for error reporting, they become so much more versatile.
For instance
#include <stdarg.h>
void my_error(FILE *out, const char *fmt, ...)
{
va_list ap;
va_start(ap_fmt);
vfprintf(out, fmt, ap);
va_end(ap);
}
Which could be invoked like this (note, I'm assuming a c99 compiler):
my_error(stderr,
"%s: Invalid range of %ld near line %d", __func__, range, __LINE__);
This could easily tie in with other answers suggesting that error codes could be defined in enumerated lists, with a constant array of strings to translate them. I'll leave that as an exercise for the reader. Its very easy to make the example above accept more arguments.
NB: If you use something like char buffer[LEN] to custom format the printed string, change it from void to unsigned int, have it return the number of bytes that vsnprintf() could not print, that might be useful to the caller. The above example is 'safe', wherein you don't have to worry about over flowing some stack allocated buffer with a formatted error message of undefined length. Or, leave it as void and have it print what it can (while noting it could not print everything), up to you. The draw back to this approach is not quite knowing the length of the variadic arguments once expanded. After all, you're reporting unexpected results :)
This approach lets you help yourself more by conveying meaningful and informative error messages, as well as simply logging them to any open file.
I know that this example basically describes printf() itself. I'm posting it to show how easy it is to adapt and expand.