4

Let's say we define some error codes as macros -

#define ERR_SUCCESS 0
#define ERR_BAD_INPUT 1

...

or as an enumerated data type -

enum err_t = { ERR_SUCCESS, ERR_BAD_INPUT, ...};

and one of these ids is returned by a function such as -

int foo(); /* if foo() returns 0, it means success, etc */

Can the caller of foo() determine which identifier / name (ERR_SUCCESS, ERR_BAD_INPUT, ...) is linked to the int return value?

Praveen Kumar K S
  • 3,024
  • 1
  • 24
  • 31
user2309803
  • 541
  • 5
  • 15
  • 3
    Noi it cannot. How would you imagine it working? – n. m. could be an AI May 08 '17 at 05:54
  • You can only compare values and that's the point of the `enum`: To create identifiers with distinct values. If you want to print the names of the error codes or a short message as `strerror` does, create an array of strings or write a function to do that. – M Oehm May 08 '17 at 05:59
  • 4
    Seems like a XY problem. Can you tell us what you want to do after knowing which enum/macro was returned? If you want to branch on it, you can simply branch on the values. If you want to print it you will need a corresponding string array. – Ajay Brahmakshatriya May 08 '17 at 06:01
  • The problem is that you didn't declare the function as `err_t foo (void)`. In which case the caller would be free to assume that it only returns one of the specified enum values. – Lundin May 08 '17 at 06:36
  • Thanks to all who answered. My goal is to develop a simple portable error handling mechanism where the enum identifier can be printf'd if required. I think Keine Lust's X Macro answer (below) was the best – user2309803 May 08 '17 at 19:48

5 Answers5

7

No.

Processor macros, as the name suggested, are resolved while preprocessing before the compilation step starts. While calling, i.e. during run time, there is no reference to the names like ERR_SUCCESS, ERR_BAD_INPUT etc. and that's why you can't determine the identifier name at run time.

Simliar reasoning goes for enum too since enum creates compile time constants and you can't get the identifier name during run time.

taskinoor
  • 45,586
  • 12
  • 116
  • 142
5

Not directly, as pointed out by others those identifiers are not available at runtime, but you can use a parallel list of names (an X Macro can help):

#include <stdio.h>

#define ERRS \
    X(ERR_SUCCESS) \
    X(ERR_BAD_INPUT) \
    X(ERR_MORE)

#define X(x) x,
enum err_t {ERRS};
#undef X

#define X(x) #x,
static char *err_name[] = {ERRS};
#undef X

static int foo(void)
{
    /* ... */
    return ERR_BAD_INPUT;
}

int main(void)
{
    printf("%s\n", err_name[foo()]);
    return 0;
}

Output:

ERR_BAD_INPUT
David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • nice solution, but it depends on the identifiers (ERR_SUCCESS, ERR_BAD_INPUT, ERR_MORE, ..) being assigned to sequential ints starting from 0 (0, 1, 2, ...) by the compiler. Does ANSI C guarantee this? – user2309803 May 08 '17 at 11:58
  • @user2309803, yes, `enum`'s always starts at index 0 when you don't provide an initial value, in other words: `enum x {a = 0, b, c};` and `enum x {a, b, c};` are the same. – David Ranieri May 08 '17 at 13:41
  • 1
    Thanks Keine, I have tested your solution and it works. I am surprised how few people upvoted it. One minor issue is a comipiler warning - `warning: comma at end of enumerator list [-Wpedantic] X(ERR_FAILURE),` since I am a stickler for portability, I moved the comma from the X(x) macro definitions to ERRS defintion `#define ERROR_CODES \ X(ERR_SUCCESS), \ X(ERR_ARG_TOO_LONG), \ X(ERR_FAILURE) #define X(ec) ec typedef enum { ERROR_CODES } t_error_code; #undef X #define X(ec) #ec static char *err_name[] = { ERROR_CODES }; #undef X` – user2309803 May 08 '17 at 19:24
  • @user2309803, thank you, there is nothing wrong with the trailing comma, but you can silent the compiler using `enum err_t {ERRS ERR_END};`, and the answer is not upvoted because "abusing the preprocessor" is considered bad style, in fact, I prefer something like http://stackoverflow.com/a/3975401/1606345 – David Ranieri May 08 '17 at 19:35
  • I don't understand why this X macro solution is considered "abusing the preprocessor" but I accept it makes the code harder to read. I had considered the [array of struct table lookup strategy](http://stackoverflow.com/a/3975401/1606345) but it violates the [DRY principle] (https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) – user2309803 May 08 '17 at 20:22
  • Yes, you are right, in some sense a lookup table violates the DRY principle, in Pascal we can use `GetEnumName(TypeInfo(TPerObjectState), Ord(AObjectState));` but C doesn't provide such mechanisms and we end up writing bizarre code: http://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c :) – David Ranieri May 08 '17 at 20:41
2

Since C99, Keine Lust's macros could even be extended to set values explicitly:

#define X(x) x,
#define X_WITH_VALUE(x, v) x = v,

and:

#define X(x) [x] = #x,
#define X_WITH_VALUE(x, v) X(x)

finally a function:

char const* getName(enum E e)
{
    char const* n = e < sizeof(arr)/sizeof(*arr) ? arr[e] : NULL;
    return n ? n : "<unknown error>";
}

Edit (in response to comment): Assigning values explicitly allows to introduce (desired!) gaps and synonyms:

enum Flags
{
    None = 0,
    F0   = 1,
    F1   = 2,
    F2   = 4,
    F3   = 8,
    G3   = F3,
}

This will result in gaps in the array, though, resulting in necessity for the null pointer check in the function. If your enum values get large, the array might get huge, so the array solution might not be suitable any more...

The synonyms impose another problem, see Jens's answer. The problem is solved partially, code does work, but you do not necessarily get back the synonym originally used, but always the last defined one instead! In above example, this would be G3 even if you used F3 in your code. So you have to define the desired synonym last (which seems a little unnatural to me...) or you use the SYNONYM macro below.

Some special cases with array size might be solved with additional tricks, e. g. if you have subsequent values with a higher start value:

#define X(x) [x - Initial] = #x,
char const* n = e < sizeof(arr)/sizeof(*arr) ? arr[e - Initial] : NULL;

Interesting gets the powers of two enum above:

#define X(x, p) x = (1 << p),
enum err_t {None = 0, ERRS};

char const* getName(enum E e)
{
    if(e == None)
        return S(None); // stringification macro, yet to be defined
    // check, if power of two:
    if((unsigned int) e & ((unsigned int) e - 1))
    {
        int index = log2(e);
        if(index < sizeof(arr)/sizeof(*arr)
            return arr[index];
    }
    return "<unknown>";
}

The power of two check is coming from Sean Eron Anderson (here exactly), you find nice solutions to calculate log2 there, too, e. g. this one.

Completely different approach, suitable for any kind of distinct values (but synonyms need to be handled explicitly!):

#define X(x) x,
#define X_WITH_VALUE(x, v) x = v,
#define SYNONYM(y, x)      y = x,

#define X(x) case x: return #x;
#define X_WITH_VALUE(x, v) X(x)
#define SYNONYM(y, x)
char const* getName(enum E e)
{
    switch(e)
    {
        ERRS
        default:
            return "<unknown>";
    }
}
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • When you say _macros could even be extended to set values explicitly_, do mean generate something like `enum err_t = { ERR_SUCCESS = 0, ERR_BAD_INPUT = 1, ...};` from the preproccessor? If so, what is the benefit? Don't both definitions of `enum err_t` (with and without explicit value assignment) compile to the same object code? – user2309803 May 08 '17 at 20:48
  • @user2309803 Not necessarily. I assumed my (extension) answer being a little more general (possibly than you asked for...) - edited my answer accordingly. Still, even after my last edit, this answer is based on Keine Lust's original idea, so if you tend to accept my answer, for fairness, please stay with him instead... Upvote is appreciated, though. – Aconcagua May 09 '17 at 05:58
  • In an array initialized with designated initializers, if you repeat an index `{ …, [6] = 37, …, [6] = 21, … }`, then the last value set 'wins' (takes effect). It is likely that a compiler will warn you about the redundant initialization. – Jonathan Leffler May 09 '17 at 06:14
  • @Aconcagua you said _Not necessarily_, do you mean `enum err_t = { ERR_SUCCESS = 0, ERR_BAD_INPUT = 1, ...};` is not neccessarily the same as `enum err_t = { ERR_SUCCESS, ERR_BAD_INPUT, ...};`? This is key requirement for Keine Lust's macro solution. I have scanned [ISO/IEC 9899] (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf) but can't find a confirmation – user2309803 May 09 '17 at 19:46
  • @user2309803 *Your* example is equivalent! As long as you do not provide an explicit value, next enum value is is previous one plus 1. If you do not provide an initial value, initial value is 0. With "not necessarily", I meant the general case, as you can have enums such as `enum E { X = 2, Y = 4 }`, which is *not* equivalent to enum `E { X, Y}`, of course... – Aconcagua May 10 '17 at 08:31
1

No. This is easy to see when you realize the mapping from values to names is not bijective. Which is a fancy way to say that macros such as

#define ZERO   0
#define ZILCH  0
#define NADA   0
#define NIENTE 0

are allowed to coexist. Given 0, which is the associated name?

The same argument applies to enum identifiers.

Jens
  • 69,818
  • 15
  • 125
  • 179
0

No, it's not supported by the compiler. The compiler gets the output of the C preprocessor with the #defines replaced by there values. You need to detect the values in your code. e.g. like in this question.

Community
  • 1
  • 1
harper
  • 13,345
  • 8
  • 56
  • 105