Proposition 1: X macros
It is possible to use the concept of X macros widely used in Linux kernel source code. The idea is to provide multiple expansions of a macro named X:
#include <stdio.h>
// The X-macro
#define LIST_OF_ERRORS \
X(ERROR_MSG_1, "Failed to open file.\n") \
X(ERROR_MSG_2, "Failed to read file.\n") \
X(ERROR_MSG_3, "Failed to execute abc.\n")
// 1st definition of X to define enum error codes
#define X(e, m) e,
enum {
LIST_OF_ERRORS
};
#undef X
// 2nd definition of X to define cases in a switch
void print_error(int e)
{
#define X(e, m) case e: printf("[ERROR] : code (%d)\n\t%s", e, m); break;
switch(e) {
LIST_OF_ERRORS
default: printf("[ERROR] : unknown code (%d)\n", e);
}
#undef X
}
int main(void)
{
print_error(ERROR_MSG_2);
print_error(ERROR_MSG_1);
print_error(ERROR_MSG_3);
print_error(10);
return 0;
}
Test:
$ gcc err.c
$ ./a.out
[ERROR] : code (1)
Failed to read file.
[ERROR] : code (0)
Failed to open file.
[ERROR] : code (2)
Failed to execute abc.
[ERROR] : unknown code (10)
N.B.: This partially answers the question as I use a multi-line macro...
Proposition 2: ## operator
In this proposition you need to use "hardcoded" constant error numbers passed as parameter to PRINT_ERROR() macro otherwise the compiler will complain. The compiler will also complain if you pass unknown error numbers.
#include <stdio.h>
#define ERROR_LABEL_0 "Error message#0\n"
#define ERROR_LABEL_1 "Error message#1\n"
#define ERROR_LABEL_2 "Error message#2\n"
#define ERROR_LABEL_3 "Error message#3\n"
#define PRINT_ERROR(e) fprintf(stderr, "[ERROR] : code (%d)\n\t%s", e, ERROR_LABEL_##e)
int main(void)
{
PRINT_ERROR(2);
PRINT_ERROR(1);
PRINT_ERROR(3);
return 0;
}
Execution:
$ gcc macro.c
$ ./a.out
[ERROR] : code (2)
Error message#2
[ERROR] : code (1)
Error message#1
[ERROR] : code (3)
Error message#3
But if you use a variable or a constant out of range, the compiler complains:
[...]
int main(void)
{
int err = 2;
PRINT_ERROR(2);
PRINT_ERROR(1);
PRINT_ERROR(3);
PRINT_ERROR(err); // <---- Compilation error
PRINT_ERROR(10); // <---- Compilation error
return 0;
}
The errors displayed by the compiler:
$ gcc macro.c
macro.c: In function 'main':
macro.c:15:72: error: 'ERROR_LABEL_err' undeclared (first use in this function); did you mean 'ERROR_LABEL_1'?
15 | #define PRINT_ERROR(e) fprintf(stderr, "[ERROR] : code (%d)\n\t%s", e, ERROR_LABEL_##e)
| ^~~~~~~~~~~~
macro.c:25:3: note: in expansion of macro 'PRINT_ERROR'
25 | PRINT_ERROR(err); // <---- Compilation error
| ^~~~~~~~~~~
macro.c:15:72: note: each undeclared identifier is reported only once for each function it appears in
15 | #define PRINT_ERROR(e) fprintf(stderr, "[ERROR] : code (%d)\n\t%s", e, ERROR_LABEL_##e)
| ^~~~~~~~~~~~
macro.c:25:3: note: in expansion of macro 'PRINT_ERROR'
25 | PRINT_ERROR(err); // <---- Compilation error
| ^~~~~~~~~~~
macro.c:15:72: error: 'ERROR_LABEL_10' undeclared (first use in this function); did you mean 'ERROR_LABEL_1'?
15 | #define PRINT_ERROR(e) fprintf(stderr, "[ERROR] : code (%d)\n\t%s", e, ERROR_LABEL_##e)
| ^~~~~~~~~~~~
macro.c:26:3: note: in expansion of macro 'PRINT_ERROR'
26 | PRINT_ERROR(10); // <---- Compilation error