Intializing struct containing an union

M

Michael Brennan

I have a menu_item structure containing an union.
func is used if the menu item should use a callback,
and submenu if a popupmen should be shown.

struct menu_item {
enum { function, popup } type;
union {
int (*func)(int);
struct menu_item *submenu;
} action;
};

The problem is that I want to use an initializer to
initialize this structure.
But if I understood what I read in the FAQ right, you can't initialize
the unions in C89, only in C99 with ``designated initializers''.
I could use the C99 feature (I think, I haven't really checked it out),
but I usually try to only code in C89, so I wonder if there is some
good way to solve this in C89 (not absolutely neccessary though).
I could of course just remove the union and only use two regular
variables, func and submenu, and use just one of them depending on
the types.
Or how about using a void pointer, and casting it between a fuction
pointer or a structure pointer?

Any comments are appreciated!

/Michael Brennan
 
C

Chris Torek

I have a menu_item structure containing an union.
func is used if the menu item should use a callback,
and submenu if a popupmen should be shown.

struct menu_item {
enum { function, popup } type;
union {
int (*func)(int);
struct menu_item *submenu;
} action;
};

The problem is that I want to use an initializer to
initialize this structure.
But if I understood what I read in the FAQ right, you can't initialize
the unions in C89, only in C99 with ``designated initializers''.

More precisely, you can only initialize the *first* union member.
So if you have a function fn:

int fn(int);
struct menu_item m_fn = { function, { fn } };

which works in both C89 and C99; but if you have a popup "pu" you
need C99's:

struct menu_item pu = { popup, { .submenu = &m_fn } };

There is no completely-satisfactory solution to this problem, but
there is a method that, if you can use it at all, is likely to
work on "real implementations". Namely, write variants of the
"struct" that have each of the union members "first". In this
case:

enum menu_item_type { function, popup };

struct menu_item_if_function {
enum menu_item_type type; /* should be "function" */
union {
int (*func)(int);
struct menu_item *submenu;
} action;
};
struct menu_item_if_popup {
enum menu_item_type type; /* should be "popup" */
union {
struct menu_item *submenu;
int (*func)(int);
} action;
};

Now you can do this:

int fn(int);
struct menu_item_if_function m_fn = { function, { fn } };
struct menu_item_if_popup m_pu = { popup, { &m_fn } };

At some point, you pick one of the variants (arbitrarily) to be
the "main" one for use in the code. Let us say that in this case
you choose "menu_item_if_function" (which you might then rename
to remove the "_if_function" suffix, but for the moment I will
leave it in). Then you can make an array of pointers to the
various initialized objects (m_fn and m_pu above), and cast the
"wrong type" pointers (and hope very hard that the compiler has
not done bizarre things, so that this actually works):

struct menu_item_if_function *array[] = {
&m_fn,
(struct menu_item_if_function *)&m_pu,
NULL
};
Or how about using a void pointer, and casting it between a fuction
pointer or a structure pointer?

This fails on some Real Implementations, where "int (*)(int)" is
128 bytes long (function pointers have a lot of stuff in them) but
"void *" is only 32 bytes long (data pointers have only a little
stuff in them).

The "multiple variants of the struct" trick is much more likely to
work: it depends only on the compiler making the layout of each
struct-containing-a-union independent of the order of the union
members. By having all the same members in each "sub-union", we
guarantee that they all have *enough* room (and alignment) for
*all* their members, and they should generally all have the same
size and layout. Then, since all the "struct"s have the same
members (in terms of size and layout), all the "struct"s should
have the same alignment and so on as well.
 
J

Jack Klein

I have a menu_item structure containing an union.
func is used if the menu item should use a callback,
and submenu if a popupmen should be shown.

struct menu_item {
enum { function, popup } type;
union {
int (*func)(int);
struct menu_item *submenu;
} action;
};

The problem is that I want to use an initializer to
initialize this structure.
But if I understood what I read in the FAQ right, you can't initialize
the unions in C89, only in C99 with ``designated initializers''.

You understand it incorrectly. In C89 you may initialize the first
member of a union. You may not initialize any other member.
I could use the C99 feature (I think, I haven't really checked it out),
but I usually try to only code in C89, so I wonder if there is some
good way to solve this in C89 (not absolutely neccessary though).
I could of course just remove the union and only use two regular
variables, func and submenu, and use just one of them depending on
the types.
Or how about using a void pointer, and casting it between a fuction
pointer or a structure pointer?

There is absolutely no defined conversion whatsoever between pointers
to functions of any signature, and pointers to objects of any type.
There are platforms where the two are of different sizes.

Do you have more than 1,000,000 of these? If not, or even if so,
consider changing it to:

struct menu_item
{
int (*func)(int);
struct menu_item *submenu;
}

And initialize like this:

struct menu_item top_menu_tree [] =
{
{ func1, NULL },
{ NULL, file_menu_tree },
{ NULL, edit_menu_tree },
{ NULL, search_menu_tree },
{ NULL, tool_menu_tree },
{ show_help, NULL }
};

The enumerator is no longer needed, a simple test for NULL will do.

Consider that on typical desktop platforms today (i.e., 32-bit),
sizeof (int) == sizeof (void*) == sizeof (int (*)(int). Even if your
compiler provides an option to instantiate enumerated types in the
smallest integer type that can hold the value, it is quite possible
that alignment padding will make your structure as large as mine.

And even if your struct ends up, let's say, 3 bytes shorter than mine,
how many of them will your program actually have? What price
correctness and portability?
 
K

Keith Thompson

Chris Torek said:
This fails on some Real Implementations, where "int (*)(int)" is
128 bytes long (function pointers have a lot of stuff in them) but
"void *" is only 32 bytes long (data pointers have only a little
stuff in them).

Not that it's relevant to your point, but do you really mean 128 and
32 bytes rather than bits?
 
C

Chris Torek

Not that it's relevant to your point, but do you really mean 128 and
32 bytes rather than bits?

Not really sure; I was thinking of the AS/400, where pointers are
"ridiculously large".
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top