tags:

views:

143

answers:

6
typedef union {
    float flts[4];
    struct {
        GLfloat r;
        GLfloat theta;
        GLfloat phi;
        GLfloat w;
    };
    struct {
        GLfloat x;
        GLfloat y;
        GLfloat z;
        GLfloat w;
    };
} FltVector;

Ok, so i think i get how to use this, (or, this is how i have seen it used) ie.

FltVector fltVec1 = {{1.0f, 1.0f, 1.0f, 1.0f}};
float aaa = fltVec1.x;
etc.

But i'm not really groking how much storage has been declared by the union (4 floats? 8 floats? 12 floats?), how? and why? Also why two sets of curly braces when using FltVector {{}}?

Why use a union at all? Why not do..

   struct FltVector {
        GLfloat x;
        GLfloat y;
        GLfloat z;
        GLfloat w;
   }

?

Any pointers much appreciated (sorry for the pun)

+5  A: 

A union allows you to “recycle” the same area of memory for different types of variables. Generally the union takes as much storage as its single largest member, in this case probably 4 floats. You can check with sizeof.

In this case the union is probably used to provide 1) alternative names for the same floats in the struct (e.g. x and r share the same memory), and 2) access to the same four floats as an array (e.g. x and flts[0] share the same memory). Sometimes unions are used in various “hacks”, usually non-portable, to access the internals of some data type e.g. the individual bytes in an integer in the machine order.

Arkku
And that is in this case: 4 floats (if GLfloat equals float)
tur1ng
+4  A: 

if sizeof(GLfloat) == sizeof(float) then, 4 floats have been allocated.

flts[0], r and x will all refer to the same piece of memory here.

In a union, every different variable declared in the union refers to the same piece of memory.

Here we have 3 variables, 2 structs and an array, and each of them start at the same point in memory.

Salgar
+2  A: 

A few questions there :)

@Arkku is right about the size. Alignment can also play a part, but probably not here.

Why this is true is that at any given time the union only holds one of the possible values. For this reason it's common to have the union in a struct alongside something which identifies which value is valid (sometimes called a discriminated union or scrim).

One pair of braces is for the union, and the other for the array initalizer.

pdbartlett
A: 

why two sets of curly braces when using FltVector {{}}

Look at the whole line, FltVector fltVec1 = {{1.0f, 1.0f, 1.0f, 1.0f}}; You're initializing the four floats in the first struct in the union. As you can see from the bold "in"s, there are two levels of nesting. If the level of nesting was even deeper, you could even have more curly braces.

MSalters
@MSalters: yes, but here we could remortlessly remove one set of braces as it would initialize the first inner struct instead.
kriss
A: 

In your example, if we consider the name of the variables, the union is most certainly not used to access the same memory cell through say x and r (as a radius and an x coordinate would not fit well), but to leave user provide the same argument for both. It's much simpler to set x, y, z, w when you use cartesian coordinates and would be awkward to use these same names for radial coordinates. And both are simpler than just array indices. You probably also have another parameter around that give the type of the provided coordinate (either cartesian or radial). Thus you will have a discriminated union as pdbartlett calls them.

In this case the double level of braces is useless as the array can be initialized either through the array (double level of braces) or through one of the inner struct.

correction: the double level of braces avoid casting inputs to GLFloats.

Last detail: unnamed inner structs is not standard C, the standard way to do things is to give names to inner structs like in

typedef union {
    float flts[4];
    struct {
        float r;
        float theta;
        float phi;
        float w;
    } cartesian;
    struct {
        float x;
        float y;
        float z;
        float w;
    } radial;
} FltVector;

FltVector f = {1.0, 2.0, 3.0, 4.0 };

int main(int argc, char * argv[]){
    printf("flts[0]=%f f.radial.r=%f f.cartesian.x=%f\n",
        f.flts[0], f.radial.r, f.cartesian.x);
}
kriss
Thanks everyone for the help, it really is much clearer now.
mustISignUp
It is not "more usual", it is actually required by the language. It compiles without those names due to a non-standard compiler extension.
AndreyT
@AndreyT: I will edit the answer, I didn't check in the standard (anyway I'm not a true believer of standards and take compilers as the real thing. In my eyes stndard are just tools to avoid to much entropy and guide language evolution).
kriss
A: 

As have been noted, the code allocates the same memory using different names and datatypes. It can some times be comfortable to be allowed to work on named vector-components (xyzw), while still being able to treat the vector as an array at other times.

It looks like the names for the cartesian and the radial structs are swapped, though. "r", "theta" and "phi" are common names for radial coordinates, not cartesian ones which are usually denoted as "x", "y" and "z".

I think it's worth noting that using the different representations is not strictly standard-compliant (but probably works fine on all existing C-implementations), for two reasons:

  1. Reading a union member that is not the most recently written yields undefined result. Any sane implementation will return the value stored in that memory, though.
  2. The compiler might add padding between struct members(for performance reasons), while arrays are never padded. This is unlikely to happen in this case on any modern CPU, though.
kusma