Union trouble

B

Bartc

I'm porting some code from a language that allows casual aliases to struct
fields and between variables.

I can duplicate some of this with unions but the results look inelegant. The
first example involves structs:

#include <stdio.h>

typedef struct {
short int a,b; /* assume 16-bit short and 32-bit int in this example
*/
int c,d,e;
} vrec;

int main(void)
{static vrec v;
vrec *p = &v;

p->a = 23;
p->b = 1;

printf("V = %d %d : %d %d %d\n",v.a,v.b, v.c, v.d, v.e);
}

The above is OK, but sometimes I would like to deal with .a and .b fields as
a single 32-bit value. (My compiler should optimise the assignments of 23,1
above but it doesn't). I came up with:

#include <stdio.h>
#include <stdlib.h>

typedef struct {
union {
struct {short int a,b;}s;
int ab;
} u;

int c,d,e;
} vrec;

int main(void)
{static vrec v;
vrec *p = &v;

p->u.ab = 1<<16 | 23;

printf("V = %d %d : %d %d %d\n",v.u.s.a, v.u.s.b, v.c, v.d, v.e);
}

Works, but field access is much more untidy, and I'd prefer to hide the
mechanism I used to combine .a and .b for some operations. Is there a better
way? (Before, I was using something like:

short int a,b;
int ab @ a;)

I've tried unions to solve my other problem: 2 variables, of different
types, which share the same memory, but run into the same problems of extra
clutter being generated. Is there a simple way in C of achieving:

double x;
int a @ x; /* store a at the same address as x, or: */

char s[10];
short b @ s[2]; /* store b at same address as s[2..3] */

This is possible to achieve using complicated casts; or defining the
equivalenced variable as a macro which contains the cast needed. But both
are fiddly.
 
B

Bartc

Ok, let's simplify.

Is there a way of using unions transparently, so that the union name doesn't
get in the way?

So:

union {int a,b;} u;

a=1;

instead of:

u.a=1;

In particular when the union is inside a struct.
 
R

Richard Bos

Bartc said:
Is there a way of using unions transparently, so that the union name doesn't
get in the way?

So:

union {int a,b;} u;

a=1;

instead of:

u.a=1;
No.

In particular when the union is inside a struct.

I believe Ganuck offers this as an extension, but it's not ISO C.

Richard
 
D

David Mathog

Bartc said:
I'm porting some code from a language that allows casual aliases to struct
fields and between variables.

I can duplicate some of this with unions but the results look inelegant. The
first example involves structs:

By "casual aliases" do you mean that it sets two variables "one" and
"two" to both reference the same storage area "one_two_data"? Rather
than using Union it may be easier to just code all of these aliases into
your program explicitly, like:

int one_two_data;
int *one = &one_two_data;
int *two = &one_two_data;

If you don't want to have to write "*one" everywhere then you can
use instead:

int *fake_one = &one_two_data;
int *fake_two = &one_two_data;
#define one (*fake_one)
#define two (*fake_two)

then use "one" and "two" as the variables. So long as all the casual
aliases are to identical data types at that storage location this should
work fine.

Regards,

David Mathog
 
B

Bartc

David Mathog said:
By "casual aliases" do you mean that it sets two variables "one" and "two"
to both reference the same storage area "one_two_data"?

Yes. Although the types could be different.
Rather than using Union it may be easier to just code all of these aliases
into your program explicitly, like:

int one_two_data;
int *one = &one_two_data;
int *two = &one_two_data;

If you don't want to have to write "*one" everywhere then you can
use instead:

int *fake_one = &one_two_data;
int *fake_two = &one_two_data;
#define one (*fake_one)
#define two (*fake_two)

then use "one" and "two" as the variables. So long as all the casual
aliases are to identical data types at that storage location this should
work fine.

I suppose using pointers to the same memory was always an option, if I
couldn't avoid the extra dereference step. But at least it makes it
possible. I will try out your idea.
 
K

Keith Thompson

Bartc said:
Ok, let's simplify.

Is there a way of using unions transparently, so that the union name doesn't
get in the way?

So:

union {int a,b;} u;

a=1;

instead of:

u.a=1;

In particular when the union is inside a struct.

Here's a plausible use for such a thing:

enum foo_type { INT, DOUBLE };

struct foo {
enum foo_type type;
union {
int i;
double d;
} u;
};

int main(void)
{
struct foo f0, f1;

f0.type = INT;
f0.u.i = 42; /* want to write f0.i, not f0.u.i */

f1.type = DOUBLE;
f1.u.d = 1.3; /* want to write f1.d, not f1.u.d */

return 0;
}

As Richard Bos mentioned, gcc offers this as an extension (if you drop
the member name "u" from the struct declaration, you can refer to f0.i
and f1.d directly). But of course it's neither standard nor portable.

My advice is just to write f0.u.i and f1.u.d; a few extra keystrokes
aren't going to kill you.

But if you really feel the need to do this, here's an approach I've
seen:

enum foo_type { INT, DOUBLE };

struct foo {
enum foo_type type;
union {
int i;
double d;
} u;
#define i u.i
#define d u.d
};

int main(void)
{
struct foo f0, f1;

f0.type = INT;
f0.i = 42;

f1.type = DOUBLE;
f1.d = 1.3;

return 0;
}

In real code, you're going to have better names that "i" and "d", of
course. Note that I put the #define directives inside the struct
declaration; it doesn't matter where they are (as long as they're
after the union declaration), but putting them there is intended to
make it clear that they're logically associated with the struct.

There are always drawbacks to using macros to mess around with the
language syntax like this. For example, you now *can't* refer to
f0.u.i; it would expand to f0.u.u.i.
 
B

Ben Pfaff

Keith Thompson said:
But if you really feel the need to do this, here's an approach I've
seen:

enum foo_type { INT, DOUBLE };

struct foo {
enum foo_type type;
union {
int i;
double d;
} u;
#define i u.i
#define d u.d
};

Just as another illustration for the OP to look at, here's a
real-life example taken from a header file on my system. I'm not
sure that I recommend it as good practice, but it certainly is
seen in real life:

/* Interface request structure used for socket ioctl's. All interface
ioctl's must have parameter definitions which begin with ifr_name.
The remainder may be interface specific. */

struct ifreq
{
# define IFHWADDRLEN 6
# define IFNAMSIZ IF_NAMESIZE
union
{
char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */
} ifr_ifrn;

union
{
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short int ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
__caddr_t ifru_data;
} ifr_ifru;
};
# define ifr_name ifr_ifrn.ifrn_name /* interface name */
# define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
# define ifr_addr ifr_ifru.ifru_addr /* address */
# define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
# define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
# define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
# define ifr_flags ifr_ifru.ifru_flags /* flags */
# define ifr_metric ifr_ifru.ifru_ivalue /* metric */
# define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
# define ifr_map ifr_ifru.ifru_map /* device map */
# define ifr_slave ifr_ifru.ifru_slave /* slave device */
# define ifr_data ifr_ifru.ifru_data /* for use by interface */
# define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
# define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
# define ifr_qlen ifr_ifru.ifru_ivalue /* queue length */
# define ifr_newname ifr_ifru.ifru_newname /* New name */
 
B

Bartc

Keith Thompson said:
Here's a plausible use for such a thing:

enum foo_type { INT, DOUBLE };

struct foo {
enum foo_type type;
union {
int i;
double d;
} u;
};

int main(void)
{
struct foo f0, f1;

f0.type = INT;
f0.u.i = 42; /* want to write f0.i, not f0.u.i */

f1.type = DOUBLE;
f1.u.d = 1.3; /* want to write f1.d, not f1.u.d */

return 0;
}

As Richard Bos mentioned, gcc offers this as an extension (if you drop
the member name "u" from the struct declaration, you can refer to f0.i
and f1.d directly). But of course it's neither standard nor portable.

My advice is just to write f0.u.i and f1.u.d; a few extra keystrokes
aren't going to kill you.

Thanks, this is good news! It's not so much the keystrokes (there's more
than a few), it's maintenance of the code if the struct definition changes.

I need to translate code from another language that has a number of structs
similar to the following (although most not as bad as this one):

// word, sint are 2 bytes
// int, long, ref, istring(=ref char) are 4 bytes
// real, dint are 8 bytes
// the shared fields in use change according to .dtype

type varrec=struct (

word dtype
byte copy
byte spare

int value
int value2
int value3

// Shared fields
long dtypex @dtype

real xvalue @value
dint dvalue @value

ref byte ptr @value
istring svalue @value

int length @value2
int upper @value2

int lower @value3
int tag @value3
int pos @value3

int first @value
int last @value2

long retaddr @value
long frame @value2
long nvars @value3

int doti @value3
word rdoti @length
word rlength @length+2

word elemtype @value3
sint shortlower @value3+2
)

The problem with #defines is that I wouldn't be able to use short, informal
field names like these at file level; they would clash.
 

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,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top