C doesn't support exceptions, but we can store error messages in structs. This document compares 3 different strategies of error reporting.
typedef enum {
FILE_NOT_FOUND_ERROR,
INVALID_FILE_FORMAT_ERROR,
...
} ErrorType;
typedef struct {
ErrorType type;
char* message;
} Error;
int func1(Error* error);
int result = func1(NULL); // Here we just assume func1 will work
Error* error;
int result = func1(error);
if (error) {
...
free(error->message);
}
I find this one reads most naturally.
Error* func2(int* result);
int result;
func2(&result);
int result;
Error* error = func2(&result);
if (error) {
...
free(error->message);
}
This one's not very nice, I wouldn't use it.
bool func3(int* result, Error* error);
int result;
func3(&result, NULL);
int result;
if (!func3(&result, NULL)) {
... /* We care that func3 had an error, but we don't care what that error
* is, and so we don't need to free anything
}
int result;
Error* error;
if (!func3(&result, error)) {
...
free(error->essage);
}
This one is the most complicated but also the most flexible. This is what GLib uses. If your already using GLib in your project, see https://developer.gnome.org/glib/2.42/glib-Error-Reporting.html.