What are unions good for?

Started by
27 comments, last by Jan Wassenberg 13 years, 1 month ago

Fair enough that I didn't take great pains to enumerate potential downsides, but to be fair I gave that code no endorsement either -- I simply put it forward as one (rather common) example of its use. I didn't even pull that from my own code, but did a quick Google search and pulled it from the first hit, which happened to be a thread here on gamedev. I could have taken the time to vet the code, but I was in a rush.

In any event, if you have an issue with the code itself, lets pick it apart and demonstrate what's wrong with this (again, common -- even if platform specific) practice, rather than leveling near-accusations that I've undertaken this enterprise as some sort of malice or ignorance-driven desire to mislead.

I think the problem was that there were a number of threads in previous posts that did this already. You appeared to be adding to the "noise" of bad practise perhaps without having read the existing posts. The only thing missing is a chapter/verse in the standard, but presumably if one has a copy, it's easy enough to look it up.
Advertisement

Is that the way you drive, or want an airline to operate - "not exactly 100% safe, but generally [..] not a problem"?

Yes, that is how such industries operate.

Each aircraft launches with hundreds of "issues". They do a cost analysis. How much to fix and replace vs. cost of death. A person is valued at around $2 mil IIRC. Cost of most replacements can quickly go above a plane's worth of people. Same for medical gear, nuclear facilities, chemicals, ...

Just saying...
I've seen code that uses unions for a generic message type to pass around to multiple area. For example, if you have an Audio and Video subsystem, and a generic message type, you could do something like this (in C):



typedef enum
{
VIDEO_SUBSYSTEM=0,
AUDIO_SUBSYSTEM,
MANAGER_SUBSYSTEM,
MAX_SUBSYSTEMS
} etMsgLocation;

typedef struct
{
etAudioType eAudioType;
uint16 SampleRate;
uint16 Channels;
uint32 BufferSize;
uint8 *pBuffer;
} AudioMsg;

typedef struct
{
etVideoFormat eVideoFormat;
union
{
tMpeg4CompressData Mp4Data;
tAviCompressData AviData;
tMpeg2CompressData Mp2Data;
} o;
} tVideoMsg;

typedef struct
{
etMsgLocation eMsgDestination;
etMsgLocation eMsgSource;
union
{
tAudioMsg AudioMsg;
tVideoMsg VideoMsg;
} o;
} tSystemMsg;

tSystemError SendSystemMessage(tSystemMsg *pSystemMsg);



This code compresses audio and video specific informaiton into one structure, but shares the memory location.

Also, the Video Msg type shares memory locations with the different possible compression types.

So, this is one way I commonly see unions used.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

You mean "type punning", for which there are actually two standard and perfectly safe methods - serializing through char*, which has a special dispensation concerning aliasing, or memcpy.[/quote]

I understand that you can cast a type to char*, but it's not compliant to cast char* back into another type.
Suppose I want to bitshift a floating point value. (This is part of a neat little hack used in some 3D engines to approximate the square root operation) I'd have to do something like this right?


float num = 5416.53;
int dest;
char* cNum = reinterpret_cast<char*>(&num);
char* cDest = reinterpret_cast<char*>(&dest);
std::copy(cNum,cNum+sizeof(dest),cDest);
dest >>= 1;
std::copy(cDest,cDest+sizeof(cNum),cNum);


Clearly this is impractical if the reason for using type punning is performance. Seems far more practical to settle for the non-compliant method of using unions. At least it avoids violating strict aliasing rules which are actually enforced on certain major compilers.

Speaking of strict aliasing, how does one use the malloc function when it returns a void pointer? Isn't casting this pointer to another type considered a violation? Or is converting void* to anything else another exception along with cast to char?

I understand that you can cast a type to char*, but it's not compliant to cast char* back into another type.[/quote]
And what is this understanding based on?
You can cast until the cows come home. What is important is that all accesses to a stored value occur through the same pointer (or "by an lvalue expression that has .. character type" [ANSI C99 6.5#7]).

(This is part of a neat little hack used in some 3D engines to approximate the square root operation)[/quote]
Which used to be a good idea 10..15 years ago, but I'd expect it to be slower now than the SQRTSS instruction generated by a simple call to fsqrtf().

I'd have to do something like this right?[/quote]
Ooh. Numerous problems with that code:
- unnecessary reinterpret_cast
- incorrect end pointer in the second std::copy
- using std::copy, which must be able to handle overlapping ranges, instead of memcpy
- right-shifts on negative integers are undefined

Clearly this is impractical if the reason for using type punning is performance.[/quote]
That is anything but clear. Can you justify that statement?
After fixing the above, I see the following code generated (ICC 12.1):
000000013F7B1058 mov edx,dword ptr [dest]
000000013F7B105B shr edx,1
000000013F7B105D mov dword ptr [dest],edx

Speaking of strict aliasing, how does one use the malloc function when it returns a void pointer? Isn't casting this pointer to another type considered a violation?[/quote]
Nope. Please see ANSI C99 6.7.3#15.
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
On x86 processors, that kind of type-punning often has to write the floating-point value to memory, read it into an integer register, perform the integer operation, write it to memory again, and read it back into a floating-point register so it may not be as fast as you think it is. If you're determined to use that kind of approximation, though, there are some slightly better versions. They're faster than the normal sqrt() but less accurate. Setting the floating-point model to "Fast" in Visual C++ allows it to use a native SQRT or SQRTSS instruction and avoid the normal library function call.

Fast reciprocal square root may still have a use in performance-critical code, but even that it less useful than it once. If you're using SSE, you may be better off using RSQRTSS or the _mm_rsqrt_ss() intrinsic. (With fast floating point and SSE, the VC++ optimizer might be smart enough to replace 1 / sqrt with a reciprocal square root instruction. I wouldn't count on it, though...)
That is anything but clear. Can you justify that statement?
After fixing the above, I see the following code generated (ICC 12.1):
000000013F7B1058 mov edx,dword ptr [dest]
000000013F7B105B shr edx,1
000000013F7B105D mov dword ptr [dest],edx
[/quote]

VS2010 wouldn't optimise out the std::copy calls, but after switching to memcpy, it's now producing fully optimised source. (identical to using a straight up cast to int*)

Nope. Please see ANSI C99 6.7.3#15.[/quote]

What does the standard say on this? And do you know where I can find a copy of the standard for download?
On x86 processors, that kind of type-punning often has to write the floating-point value to memory, read it into an integer register, perform the integer operation, write it to memory again, and read it back into a floating-point register so it may not be as fast as you think it is. If you're determined to use that kind of approximation, though, there are some slightly better versions. They're faster than the normal sqrt() but less accurate. Setting the floating-point model to "Fast" in Visual C++ allows it to use a native SQRT or SQRTSS instruction and avoid the normal library function call.[/quote]

Yeah it's not worth the trouble in this day and age when you can simply enable SSE2 and FP:fast. I should have mentioned a more relevant application of type punning. For example, generating a hash values out of non-integer types.

What does the standard say on this?[/quote]
Reading single paragraphs is not the path to wisdom :)

And do you know where I can find a copy of the standard for download?[/quote]
http://lmgtfy.com/?q...2+%2Bc+standard
I don't think you'll find anything but the draft C++0x standard for free legal download, but $30 should be affordable.


For example, generating a hash values out of non-integer types.[/quote]
Ah, that's a good example.
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3

This topic is closed to new replies.

Advertisement