I have had occasion where I've needed to do something like this:
struct SBGRA
{
unsigned char blu;
unsigned char grn;
unsigned char red;
unsigned char alp;
};
The standard allows an implementation to insert arbitrary padding
between members of a struct. This is extremely unlikely when the members
are all of character type, because there's no possibility of alignment
issues that would require padding. However, since your code absolutely
depends upon the absence of such padding, you really should be using a C
construct which actually forbids padding, such as unsigned char[4],
rather than a struct.
The return type of main must be int. Many text books get this wrong, and
many people write their code accordingly, and as a result many compilers
accept such code - those books and those people are all ignoring what
the C standard says. Those compilers can actually be fully conforming -
the behavior of such a program is undefined, which means it's perfectly
acceptable for the implementation to translate it the way you've
incorrectly assumed it must be translated.. However, a decent compiler
should warn you about this mistake.
{
struct SBGRA color = { 255,255,255,255 }; // white, full alpha
foo(color); // do something with that color
}
void foo(SBGRA color)
{
union {
struct SBGRA color;
unsigned int _color;
} u;
There are many contexts where you're prohibited from defining an
identifier that starts with '_'. This is NOT one of those contexts.
However, can you tell me precisely when it is safe to define such an
identifier? If not, I recommend avoiding them entirely, at least until
you've had time to memorize the relevant rules (section 7.1.3 of the C
standard).
From past experience with you, I doubt that you're concerned about
portability - but the C standard provides no guarantees that unsigned
int is long enough to alias struct SBGRA, even if it had no padding.
unsigned char is allowed to have more than 8 bits, and there are real,
modern machines where it has 16 bits. unsigned int is allowed to have as
few as 16 bits, and there are real, modern machines where it is. You
should use uint8_t and uint32_t, respectively, from <stdint.h>. Those
are optional types, but it's rather unlikely that either of them is
unsupported. On any platform where either one is not supported, nothing
remotely similar to what you've written will work, so it's a good idea
to write your code in such a way that it would fail at compile time with
a diagnostic on such a platform.
There's also another and much more plausible portability issue, which
you might be equally unconcerned about. Even assuming you are using
uint8_t[4] and uint32_t, there's still no guarantees about which bits in
u._color correspond to each element of u.color. In particular, there's
the big-endian/little-endian issue. If you need to port this code
between big-endian and little-endian machines, you'll need to decide
whether the byte order imposed by u.color or the bit order of u._color
is more important. I would expect the byte order of u.color is the
relevant one, and I'll use that assumption below.
// Convert SBGRA to an unsigned int
u.color = color;
some_other_function(u._color); // Do something with that number
}
-----
Is there a way to do something like this in C (to declare a union in
the function definition line)?
void foo(union { SBGRA color, unsigned int _color } u)
{
some_other_function(u._color);
}
In order to call foo() with defined behavior, you must pass it an object
of a type compatible with the unnamed type of u. Since that type is
unnamed, that is, in fact, extremely difficult (but possible) to
arrange. I wrote up a long explanation of why it was difficult, and how
it could be arranged, with the net conclusion that it makes much more
sense to give up your insistence that the union be anonymous. It is far
easier to make foo() usable if you give the union either a tag name or a
typedef.
However, after putting a lot of effort into that explanation, I realized
that it's probably irrelevant, because I'm almost certain that you don't
want to create an object of compatible type to pass to foo(). I suspect
that you want to pass either a struct SBGRA or an unsigned int to foo(),
possibly both, and neither of those types is compatible with the type of
u. You can't make anything like that work portably in C.
There's probably many implementations where you could get it to work if
you use a K&R style declaration for foo() rather than a function
prototype, in any translation unit where foo() is called. Note that the
definition of foo() given above includes a function prototype:
therefore, if you wish to call foo() from later on within the same
translation unit where it is defined, you should change the definition
accordingly. I would not recommend that approach, but if you're
completely unconcerned with portability, go ahead and give it a try, it
might work (or it might not).
The right way to do this is to pass around uint32_t values, and then
using constructs such as ((uint8_t*)color)[2] to access the red byte of
the object that it's stored in.