-2

I have defined some macros like this:

#define ABC '1'
#define DEF '2'
#define XYZ '3'

And I also need to output the macro string based on its value, like this:

static const char* get_event_string(unsigned char event)
{
    switch (event) {
        case '1':
            return "ABC";
        case '2':
            return "DEF";
        case '3':
            return "XYZ";
    }
}

Is there any simple way to do it?

Tom Xue
  • 3,169
  • 7
  • 40
  • 77
  • Seems simple enough. What exactly do you try to achieve? – Yunnosch Jun 19 '17 at 16:14
  • 2
    Why are you using `'1'` in your case statements when you have perfectly usable `#define`'d labels set up? – Chris Turner Jun 19 '17 at 16:15
  • Close enough if you don't mind using enums instead: https://stackoverflow.com/questions/9907160/how-to-convert-enum-names-to-string-in-c – HolyBlackCat Jun 19 '17 at 16:23
  • Is there a compelling reason to have actual *digits* (as encoded characters) for your `event`? If not, suggest to just use numbers starting from `0` (or from `1` if you need `0` for some special value like "no event") -- this would also make the alternative approach shown in my answer *simpler* (you wouldn't even *need* a function any more) and enable you to use a preprocessor trick like in the question linked by @HolyBlackCat. –  Jun 19 '17 at 16:27
  • Are `ABC,DEF,XYZ` sequential values like `'1','2','3'`? – chux - Reinstate Monica Jun 19 '17 at 16:31
  • 1
    too broad, please detail what you want, here we don't really understand what you want. – Stargateur Jun 19 '17 at 16:49

2 Answers2

2

Be sure to check out this fine answer first: How to convert enum names to string in c


If that does not meet your needs, compute an index based on the various constants.

If the constants ABC,DEF,XYZ are unique, arbitrary (e.g. maybe not sequential) and the constant count fixed (3), could use a formula and let the compiler optimize.

const char* get_event_string(unsigned char event) {
  int index = (event == ABC)*1
            | (event == DEF)*2
            | (event == XYZ)*3;
  static const char *event_string[4] = { "None", "ABC", "DEF", "XYZ" };
  return event_string[index];
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • Hmm, sure this is better than letting the compiler optimize a `switch .. case`? I find it less readable, but that's probably a matter of taste. –  Jun 19 '17 at 17:00
  • @FelixPalmen Good point. No suggestion that this is "better" in emitted code than a `switch .. case`. Depending on OP's needs, this does place the `event_string[]` as an array that may be easier to maintain. Of course one could code `case '1': return event_string[1]; case '2': return event_string[2]; ...` IMO best to code for clarity and using a `switch ... case` is _usually_ clear enough yet OP's needs may be different than my experience. – chux - Reinstate Monica Jun 19 '17 at 17:08
  • Should not be a concern for 10-20 strings, but wont log(n) range comparisons be better than n equality checks? – Ajay Brahmakshatriya Jun 19 '17 at 17:09
  • @AjayBrahmakshatriya if the values are really arbitrarily distributed, you will have these equality checks when claculating the `index`. So, still unsure about that. But it's definitely an interesting approach :) –  Jun 19 '17 at 17:11
  • 2
    @AjayBrahmakshatriya For large `n`, some hash or other algorithmic computation is even faster. – chux - Reinstate Monica Jun 19 '17 at 17:11
  • @FelixPalmen even if the values are arbitrarily distributed, you can still arrange them in ascending order and divide them into groups of 2 repeatedly, effectively giving log(n) comparisons. Think of it like a completely balanced binary tree converted to a decision tree. – Ajay Brahmakshatriya Jun 19 '17 at 17:14
  • @chux, of course! Nothing beats a hash function, especially when the actual keys are known, one can always write a collision free hash function that is atleast as fast as O(log (n)). – Ajay Brahmakshatriya Jun 19 '17 at 17:15
  • 1
    @AjayBrahmakshatriya True,. Even is the actual key values are not known at coding time, auxiliary SW can divine an efficient hash function just prior to compiler time with _make_. – chux - Reinstate Monica Jun 19 '17 at 17:18
1

What you have is already a common way to do it. I'd recommend to keep it this way and just actually use your macros in the case statements instead of the magic chars.

If your values are contiguous, you could also use a lookup table like this:

static const char *get_event_string(unsigned char event)
{
    static const char *const names[] = {
        "ABC",
        "DEF",
        "XYZ"
    };
    return names[event - '1'];
}

Both approaches assume that the functions are never called with invalid parameters.


If you can change the values of your events to natural numbers like e.g.

#define ABC 1
#define DEF 2
#define XYZ 3

or maybe even using an enum:

enum event
{
    EV_NONE,
    EV_ABC,
    EV_DEF,
    EV_XYZ
};

then a lookup table will have a real benefit: You don't need a function any more. Just define it (as an example for the enum above):

const char *const event_strings[] = {
    "EV_NONE",
    "EV_ABC",
    "EV_DEF",
    "EV_XYZ"
};

And all you have to write in code to access the name is event_strings[event].

Doing it this way even enables you to use the preprocessor to automate defining a matching table to your enum like shown in this answer.

  • That has too many redundancies and is error-prone. The least would be to use designated initialisers for the array. And as you already use a function to retriever the data, it would be better to move the table to function-scope and make the table itself `const`, too. – too honest for this site Jun 19 '17 at 16:32
  • Well I wouldn't use *digit characters* as IDs in my code, to start with. Maybe I'll edit this to show the (IMHO) best way when you just use natural numbers as IDs.... –  Jun 19 '17 at 16:34
  • actually good hint about the `const` table, that's better (no matter whether using a function or not) –  Jun 19 '17 at 16:53
  • A function with a local `static` if better wrt information hiding and namespace-pollution. For a longer table, I agree this is better at file-scope, but it definitively should have internal linkage. – too honest for this site Jun 19 '17 at 16:55
  • @Olaf I disagree about internal linkage as soon as there isn't a function any more. And I *have* internal linkage in the example *with* a function. In this case, putting the table inside function scope seems sensible. –  Jun 19 '17 at 16:56
  • What if the name-space is reduced? If the access changes? Using a getter-function is the recommended way; one should not hav global data between modules. That also leaves range-checking/assertions to a single function, not sprinkled all around the code using that table. It also simplifies debugging, etc. There is a reason global variables are considered bad practice. The little - if any speed improvement is negligible. Using LTO completely negates this as an advantage. – too honest for this site Jun 19 '17 at 17:00
  • 1
    @Olaf you should know very well that there's no "absolute rule" in programming and a constant lookup table for some enum names is IMHO a good use of a "shared global". –  Jun 19 '17 at 17:03
  • I do very well, but as we can't cover all areas , we should present best practice; exceptions can be learned later when necessary. A function is much more flexible without changing the interface. Direct access to a table is problematic (see my assertion/debug issue) and too inflexible. – too honest for this site Jun 19 '17 at 17:11