LinuxQuestions.org
Help answer threads with 0 replies.
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 05-22-2023, 08:07 PM   #1
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,080
Blog Entries: 23

Rep: Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063
Enum reflection in C


I am rewriting an application which has a requirement to obtain the name of an enum identifier from it's value, an operation called reflection in many languages. To my knowledge there is no native way to do this in plain old C.

The original applicaiton defined a reflection function for these particular enum values (approx. 90) which consists of a switch with a case for each value which returns a pointer to the identifier names. The case list was maintained manually when the enum definition changed.

I initially wrote a simple sed script to generate the case statements from the header which defines the enum, but still had to copy/paste the result into the source file... not too bad once in a while but that will change frequently during the rewrite and will become a problem.

So I have split out that enum definition into a separate header to ease parsing it, and written a simple script to generate another source file just for the reflection function which is invoked by the Makefile rules when the definition changes. This works well enough and allows the header to be included independently of the reflection source for those modules which do not require the reflection function, and can easily be reused for other enums and projects with minimal configuration, at the expense of the additional Makefile rules and proliferation of header files.

The result is that this header (defs.h)...

Code:
enum myenum {
   MY_RED=100,
   MY_BLUE,
   MY_GREEN,
   MY_BIRD,
   MY_CAT,
   MY_DOG
 };
Produces this source which I write to defs.c...

Code:
/* This source file generated from defs.h by ./make_defs_c.sh Mon May 22 17:30:57 MDT 2023 */

#include "defs.h"

const char *myenum2name(enum myenum type)
{
   switch(type)
      {
         case MY_RED: return "MY_RED";
         case MY_BLUE: return "MY_BLUE";
         case MY_GREEN: return "MY_GREEN";
         case MY_BIRD: return "MY_BIRD";
         case MY_CAT: return "MY_CAT";
         case MY_DOG: return "MY_DOG";
         default: return "Unknown";
      }
}
The relevant Makefile rules for an example application, where make_defs_c.sh is the generating script, are...

Code:
reflect: defs.c reflection.c
        ${CC} -o $@ reflection.c

defs.c: defs.h
        ./make_defs_c.sh > $@
But I have wondered how others approach this problem with plain old C.

A DDG search produces many hits for C++ and C-pound-sign, but nothing very useful for C. Have any of you good folks here at LQ had to do this and/or have any thoughts to share?

Thanks in advance!
 
Old 05-22-2023, 11:20 PM   #2
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 20,224

Rep: Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834
yes, I think there is no native way.
I would create a list of values/names in a file (not c or h), and generate all the required c and h files based on that, in a makefile.
 
1 members found this post helpful.
Old 05-23-2023, 06:52 AM   #3
GazL
LQ Veteran
 
Registered: May 2008
Distribution: CRUX 3.7
Posts: 6,637

Rep: Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765
You could also do something like this:
Code:
enum example { RED=0, GREEN, BLUE };
char const * const example_names[] = { "RED", "GREEN", "BLUE" };

#define EXAMPLE_NAME(n) ( example_names[(n)] )
Should be easy to auto generate the two declarations, but it's going to be beyond CPP, so you're going to have to use some sort of external tool, whether that be shell-script, awk, or even venerable old 'm4'.
 
1 members found this post helpful.
Old 05-23-2023, 06:58 AM   #4
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 924

Rep: Reputation: 445Reputation: 445Reputation: 445Reputation: 445Reputation: 445
That can't be done directly, but the C preprocessor can turn symbol names into strings. This is useful for creating an array of symbol names and addresses.

Code:
struct ESTRUCT {
  const char *name;
  void *ptr;
};

#define EXAMPLE(x) {#x, &x}

ESTRUCT mytable[] = {
  EXAMPLE (var1),
  EXAMPLE (var2),
  EXAMPLE (var3)
};
Ed
 
2 members found this post helpful.
Old 05-23-2023, 08:11 AM   #5
ntubski
Senior Member
 
Registered: Nov 2005
Distribution: Debian, Arch
Posts: 3,707

Rep: Reputation: 2028Reputation: 2028Reputation: 2028Reputation: 2028Reputation: 2028Reputation: 2028Reputation: 2028Reputation: 2028Reputation: 2028Reputation: 2028Reputation: 2028
Quote:
Originally Posted by EdGr View Post
That can't be done directly, but the C preprocessor can turn symbol names into strings. This is useful for creating an array of symbol names and addresses.
I seem to recall seeing a technique similar to this, but with two sets of definitions for the macros where one of them creates the enum { ... } definition, and the other produces the string table.
 
1 members found this post helpful.
Old 05-26-2023, 11:33 AM   #6
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,080

Original Poster
Blog Entries: 23

Rep: Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063
Quote:
Originally Posted by pan64 View Post
yes, I think there is no native way.
I would create a list of values/names in a file (not c or h), and generate all the required c and h files based on that, in a makefile.
My reason for generating the c-source from the header file itself was that it seemed it would be more readable/meaningful for myself to maintain the list in native C enum declaration syntax.

May I ask why you would prefer to generate both from a third file?
 
Old 05-26-2023, 11:55 AM   #7
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,080

Original Poster
Blog Entries: 23

Rep: Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063
Quote:
Originally Posted by GazL View Post
You could also do something like this:
Code:
enum example { RED=0, GREEN, BLUE };
char const * const example_names[] = { "RED", "GREEN", "BLUE" };

#define EXAMPLE_NAME(n) ( example_names[(n)] )
Should be easy to auto generate the two declarations, but it's going to be beyond CPP, so you're going to have to use some sort of external tool, whether that be shell-script, awk, or even venerable old 'm4'.
Mapping the constant values to names via an indexed array is the most straight forward way to provide runtime storage and access. I like the parameterized #define as a convenient way of referencing them.

And I agree, there seems no way of avoiding use of an external script to generate those declarations at compile time. I had hoped to find a clever and simple way of doing this all in the preprocessor but that now seems unrealistic. I was wrong - see clever, simple and elegant preprocessor only method provided by wainamoinen in post #11 below!

A subtle complication of using an indexed array this way is that it introduces a restriction on those constant values in that they need to be zero based and contiguous or nearly so. It woulld be difficult to allow for constants with large offsets from zero, or for disjoint sub-ranges of constant values. Both of these are common and useful and the second, disjoint sub-ranges of constant values is important to my current use (the offset value in my example was intended to suggest this).

This last point kept me focused on use of a switch, actually.

Thanks!

Last edited by astrogeek; 05-26-2023 at 11:11 PM.
 
1 members found this post helpful.
Old 05-26-2023, 12:06 PM   #8
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,080

Original Poster
Blog Entries: 23

Rep: Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063
Quote:
Originally Posted by EdGr View Post
That can't be done directly, but the C preprocessor can turn symbol names into strings. This is useful for creating an array of symbol names and addresses.

Code:
struct ESTRUCT {
  const char *name;
  void *ptr;
};

#define EXAMPLE(x) {#x, &x}

ESTRUCT mytable[] = {
  EXAMPLE (var1),
  EXAMPLE (var2),
  EXAMPLE (var3)
};
Ed
Enum constants are evaluated and replaced by the preprocessor and have no runtime storage of their own, so I am confused by your example. Assuming the x parameter is intended to be the enum identifier name you cannot take its address.

Or did I completely miss your point (very possible!).

But I appreciate the example and reminder of preprocessor quoting capability. Although aware of it, I don't think I have ever made use of it myself... food for thought.

Thanks!

Last edited by astrogeek; 05-26-2023 at 12:48 PM.
 
Old 05-26-2023, 01:18 PM   #9
pan64
LQ Addict
 
Registered: Mar 2012
Location: Hungary
Distribution: debian/ubuntu/suse ...
Posts: 20,224

Rep: Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834Reputation: 6834
Quote:
Originally Posted by astrogeek View Post
My reason for generating the c-source from the header file itself was that it seemed it would be more readable/meaningful for myself to maintain the list in native C enum declaration syntax.

May I ask why you would prefer to generate both from a third file?
Code written in c is not really suitable for this (although not impossible). A simple text file can be parsed easily and generate include and source files together. It's like idl (but much simpler).
 
2 members found this post helpful.
Old 05-26-2023, 03:03 PM   #10
EdGr
Member
 
Registered: Dec 2010
Location: California, USA
Distribution: I run my own OS
Posts: 924

Rep: Reputation: 445Reputation: 445Reputation: 445Reputation: 445Reputation: 445
astrogeek - The address is for variables with storage. For an enum, you would use the value instead.

You still have to build the table and write code to look it up.
Ed
 
1 members found this post helpful.
Old 05-26-2023, 04:42 PM   #11
wainamoinen
LQ Newbie
 
Registered: Sep 2009
Location: Belgium
Distribution: Slackware
Posts: 28

Rep: Reputation: 18
There is a native way, but requires the use of #defines. The method is called X Macro, not widely known.

Wikipedia has an article about it.

The code is below. Note that the enum entries and the switch cases are "automatically" created.

Code:
#include <stdio.h>


#define ITEMS(I) \
	I(MY_RED  , 100) \
	I(MY_BLUE , 101) \
	I(MY_GREEN, 102) \
	I(MY_BIRD , 103) \
	I(MY_CAT  , 104) \
	I(MY_DOG  , 105)


#define ENUM_ENTRY(name, id) name = id,
enum my_enum
{
	ITEMS(ENUM_ENTRY)
};


#define CASE_ENTRY(name, id) case name: return #name;
const char* MyEnumToName(enum my_enum type)
{
	switch (type)
	{
		ITEMS(CASE_ENTRY)
	};
	
	return "not found";
}


int main(void)
{
	for (int i = 100; i < 106; ++i)
	{
		printf("%s = %i\n", MyEnumToName(i), i);
	}
	
	return 0;
}
The output is:
Code:
$ ./a.out 
MY_RED = 100
MY_BLUE = 101
MY_GREEN = 102
MY_BIRD = 103
MY_CAT = 104
MY_DOG = 105
 
3 members found this post helpful.
Old 05-26-2023, 05:22 PM   #12
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,080

Original Poster
Blog Entries: 23

Rep: Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063
Quote:
Originally Posted by wainamoinen View Post
There is a native way, but requires the use of #defines. The method is called X Macro, not widely known.

Wikipedia has an article about it.
I had prepared a reply to the previous posts to wrap this thread up, but your post has stopped the presses! I had decided the switch was the way to go and this looks like it might be the way to do it without an external script - thanks!

The only initial drawback I see is that I would have to assign the value to each enum name and would lose the convenience of automatically generated sequential values. I may be able to adjust the ENUM_ENTRY macro to allow for that, or it may not be a problem that needs to be solved.

I'll see what I can do with it and reply again, but thanks for the post, very helpful!
 
Old 05-26-2023, 06:37 PM   #13
astrogeek
Moderator
 
Registered: Oct 2008
Distribution: Slackware [64]-X.{0|1|2|37|-current} ::12<=X<=15, FreeBSD_12{.0|.1}
Posts: 6,080

Original Poster
Blog Entries: 23

Rep: Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063Reputation: 4063
If I include the '=' sign as part of the id and change the definition of ENUM_ENTRY as below...

Code:
#define ITEMS(I) \
        I(MY_RED  , =100) \
        I(MY_BLUE , ) \
        I(MY_GREEN, ) \
        I(MY_BIRD , =200) \
        I(MY_CAT  , ) \
        I(MY_DOG  , ) \
        I(MY_MAX, )

#define ENUM_ENTRY(name, id)  name id,
... I get auto generated sequences of constants.

This simple change to your example code...

Code:
int main(void)
{
        const char *name;
        for (int i = 100; i <= MY_MAX; ++i)
        {
                name = MyEnumToName(i);
                if(*name != 'n')
                        printf("%s = %i\n", name, i);
        }

        return 0;
}
...produces the following result for the above enum/switch definition...

Code:
./a.out
MY_RED = 100
MY_BLUE = 101
MY_GREEN = 102
MY_BIRD = 200
MY_CAT = 201
MY_DOG = 202
MY_MAX = 203
That allows the full convenience of auto generated enum constants with no great syntactic burden and no reliance on external scripts.

Thanks, and take a bow!

Last edited by astrogeek; 05-26-2023 at 06:40 PM.
 
3 members found this post helpful.
Old 05-27-2023, 05:56 AM   #14
GazL
LQ Veteran
 
Registered: May 2008
Distribution: CRUX 3.7
Posts: 6,637

Rep: Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765Reputation: 4765
Yes, that's very clever and something I haven't seen before.
 
Old 05-27-2023, 10:22 AM   #15
NevemTeve
Senior Member
 
Registered: Oct 2011
Location: Budapest
Distribution: Debian/GNU/Linux, AIX
Posts: 4,479
Blog Entries: 1

Rep: Reputation: 1695Reputation: 1695Reputation: 1695Reputation: 1695Reputation: 1695Reputation: 1695Reputation: 1695Reputation: 1695Reputation: 1695Reputation: 1695Reputation: 1695
Anyways, if it changes so often, it should be handled run-time (read from file/DB).
 
  


Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Questions about enum. What would be a good use of enum? RHLinuxGUY Programming 5 06-11-2006 01:13 PM
enum inheritance in C++? graemef Programming 2 01-27-2006 10:14 PM
HOTSWAP: where to find #ENUM signal? iflorea Programming 0 11-20-2005 06:58 AM
C Enum exvor Programming 3 09-27-2005 02:51 PM
C, enum confusion cigarstub Programming 2 09-27-2005 09:59 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 01:40 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration