Unions

Hi guys I am kind of confused I was reading the SDL API documentation on events

can read it here and correct me if I'm wrong
https://wiki.libsdl.org/SDL_Event

anyway the event object or class is a unuion which has different objects in this case structs as fields which include SDL_KeyboardEvent

so I pass the mem address of event an SDL_Event object into the SDL_PollEvent() function,I'm guessing this function checks for events

but the problem and thing that confuses me, how are we checking if event.type == SDL_KEYDOWN because the SDL_Event class does not have a type field,the SDL_KeyboardEvent does,so surely if we wanted to check this we would need to check the events SDL_KeyboardEvents field?

here is the keyboardEvents documentation too,it's very short

https://wiki.libsdl.org/SDL_KeyboardEvent


if anyone could please clear this up for me that would be amazing =)

thanks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

   SDL_Event event;

  while(SDL_PollEvent(&event)){

		   if(event.type == SDL_KEYDOWN){


			   cout << "key down" << endl;
		   }


   		  if(event.type == SDL_QUIT){

   			  quit = true;
   		  }
adam2016 wrote:
the SDL_Event class does not have a type field

Look again at the top of https://wiki.libsdl.org/SDL_Event

SDL Wiki wrote:
Uint32        type        event type, shared with all events
thanks for the reply Peter

so it does indeed have one

it says event type, shared with all events

how can it be shared? I'm not sure how that would be implemented or possible?

thanks
two follow ups

first I tried declaring

Uint32 *number = new Uint32;

in a normal hello world program and the compiler won't recognise Uint32 how come?

also

is the types such as event as SDL_KEYDOWN are they of type Uint32 or are they an enum?

because in the documentation it says

Related Enumerations

SDL_EventType


thanks
i think you need stdint header to use these renames of integer types. They are extensions to the keyword names like unsigned int that ensure #of bits.
Uint* and Sint* are declared by SDL for portability.
thanks guys,I'm still not sure though if the types SDL_KEYDOWN,SDL_KEYUP are enums or uint32


also

Uint32 type event type, shared with all events



how can this field type be shared with all objects or fields in the Event union?

thanks
for example,

how come we are allowed to write

1
2
3
4
5
6
7
8
while(SDL_PollEvent(&event)){

      if(event.type == SDL_KEYDOWN){


	   cout << "key down" << endl;
      }



as opposed to

1
2
3
4
5
6
7
8
9
10

while(SDL_PollEvent(&event)){

   if(event.key.type == SDL_KEYDOWN){


	   cout << "key down" << endl;
   }



both ways work but the second way seems more logical and correct I don't understand how the first way works at all,now I'm guessing it is because the types are shared with all events,BUT I don't understand what that means how can the event type(field) be shared between all events(objects)?

thanks
Last edited on
You can perform both checks because both SDL_Event and SDL_KeyboardEvent have a 'type' member. You should not check event.key.type before checking event.type, because you don't know how the union has been initialized. You don't know if event.key.type contains garbage from a different struct in the union, and if its value just happens to coincide with the value of SDL_KEYDOWN.

In reality the structure of SDL_Event is kind of lame. A better-structured check would look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
switch (event.type){
    case SDL_KEYBOARD:
        switch (event.key.type){
            case SDL_KEYDOWN:
                //...
            case SDL_KEYUP:
            //...
        }
    case SDL_MOUSE:
        //...
    case SDL_JOYSTICK:
        //...
}
thanks helios,that makes sense =)
A union basically means all fields overlap in memory so you can only use one field at a time. The size of a union is normally the same as the size of the biggest field.

If SDL_Event was a struct instead of a union it would be huge because it would contain all the data of all the different event types. If a new type of event were added to the library it would increase the size even further. By making SDL_Event a union the size gets much smaller because it only need to be big enough to store the largest field. Adding a new type of event would not increase the size of the union unless it's bigger than the previously biggest field.

The first field of all the different event types are Uint32 type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct SDL_KeyboardEvent
{
    Uint32 type; 
    Uint32 timestamp;
    Uint32 windowID;
    Uint8 state;
    Uint8 repeat;
    Uint8 padding2;
    Uint8 padding3;
    SDL_Keysym keysym;
};

struct SDL_MouseButtonEvent
{
    Uint32 type; 
    Uint32 type;
    Uint32 timestamp;
    Uint32 windowID;
    Uint32 which;
    Uint8 button;
    Uint8 state;
    Uint8 clicks;
    Uint8 padding1;
    Sint32 x;
    Sint32 y;
};

// Same for all other event types... 

This means all the type fields of all the events will be stored on the same memory address inside the union. This situation is an exception to the rule that said you can only use one field of a union at a time. So if it's fine to read the type field from one of the events it's also perfectly fine to read the type field from any of the other events.

Unfortunately, I think neither C nor C++ gives the same guarantees about the type field of the SDL_Event, so I'm less sure about this part. It seems like they are relying on some non-standard feature that is widely supported by the compilers.


SDL_KEYDOWN and SDL_KEYUP are enumerators of type SDL_EventType but the library seems to use them as Uint32 most of the time. I'm not sure why but maybe they want to be guaranteed a 32-bit size. All the enumerators have positive values so they can be implicitly converted to Uint32 without problem (except loss of type safety).
Last edited on
> I think neither C nor C++ gives the same guarantees about the type field of the SDL_Event

Well, the standard has this:
12.2/25 If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. ...
[ Note: The object and its first subobject are pointer-interconvertible. — end note ]
http://eel.is/c++draft/class.mem

So, both event.type and event.<any arbitrarily picked member struct of this union>.type would be well-formed and would refer to the same object.
Mmh... It's not obvious to me that your last sentence can be deduced from that quote. It doesn't seem that this:
1
2
3
4
5
6
7
8
9
10
11
12
13
struct A{
    int a;
    union{
        int c;
    } b;
};

struct B{
    A a;
};

B b;
assert((uintptr_t)&b == (uintptr_t)&b.a);
implies this:
1
2
A a;
assert((uintptr_t)&a.a == (uintptr_t)&a.b.c);

I think that second behavior would be very shocking.

PS: GCC seems to agree with me.
Last edited on
> I think that second behavior would be very shocking.

It should be. Two different non-static data members of a non-union class type can't be placed at the same offset.

This snippet may be of some help in understanding the clear difference between union and non-union class/struct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <cstddef>

struct B
{
    int b ;
    int other ;
};

struct C
{
    int c ;
    int other ;
};

struct D
{
    int d ;
    int other ;
};

union A
{
    int a ;
    B bb ;
    C cc ;
    D dd ;
};


int main()
{
    // standard-layout union (all non-static data members are at an offset of zero)
    static_assert( offsetof(A,a) == 0, "must be at a zero offset" ) ;
    static_assert( offsetof(A,bb) == 0, "must be at a zero offset" ) ;
    static_assert( offsetof(A,cc) == 0, "must be at a zero offset" ) ;
    static_assert( offsetof(A,dd) == 0, "must be at a zero offset" ) ;

    // standard-layout struct (first non-static data member must be at an offset of zero)
    static_assert( offsetof(B,b) == 0, "must be at a zero offset" ) ;
    static_assert( offsetof(D,d) == 0, "must be at a zero offset" ) ;
    static_assert( offsetof(C,c) == 0, "must be at a zero offset" ) ;
    // standard-layout struct (the second non-static data member must be at an offset greater than zero)
    static_assert( offsetof(B,other) > 0, "must be at an offset greater than zero" ) ;
    static_assert( offsetof(C,other) > 0, "must be at an offset greater than zero" ) ;
    static_assert( offsetof(D,other) > 0, "must be at an offset greater than zero" ) ;

    constexpr A obj {} ;
    static_assert( &obj.a == &(obj.bb.b), "must be at the same address" ) ;
    static_assert( &obj.bb.b == &(obj.cc.c), "must be at the same address" ) ;
    static_assert( &obj.cc.c == &(obj.dd.d), "must be at the same address" ) ;

    A aa { 72 };
    std::cout << aa.a << ' ' << aa.bb.b << ' ' << aa.cc.c << ' ' << aa.dd.d << '\n' ; // 72 72 72 72

    aa.bb.b = 34 ;
    std::cout << aa.a << ' ' << aa.bb.b << ' ' << aa.cc.c << ' ' << aa.dd.d << '\n' ; // 34 34 34 34

    aa.dd.d = 99 ;
    std::cout << aa.a << ' ' << aa.bb.b << ' ' << aa.cc.c << ' ' << aa.dd.d << '\n' ; // 99 99 99 99
}

http://coliru.stacked-crooked.com/a/b853b86980bebad4
I know how unions work, a'ight. I was just under the misapprehension that SDL_Event was a struct, rather than a union.
Thank you JLBorges! I learned something new. I didn't know simple structs were not allowed to have padding or other hidden data at front.

But I still have a few doubts left. The standard says the following about unions.

In a union, at most one of the non-static data members can be active at any time, that is, the value of at
most one of the non-static data members can be stored in a union at any time.

If what you said were true it would lead to two non-static data members being active at the same time. E.g. if the event type is SDL_KEYDOWN, both type and key would be active. But the standard says this should never be the case, except when the members are standard-layout structs that share a common initial sequence but that doesn't apply here.
Last edited on
What that means is that if you write to one union member you later have to read from the same union member, otherwise the behavior is undefined.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
union foo{
    struct{
        int a;
        int b;
    } a;
    int b;
};

foo f;
f.a.a = 1;
f.a.b = 2;
#ifdef WANT_UNDEFINED_BEHAVIOR
std::cout << "Mystery value: " << f.b << std::endl;
#else
std::cout << "Known value: " << f.a.a << std::endl;
#endif 


IINM C11 has made this behavior defined in some cases.
Last edited on
I don't think we can say that more than one non-static data member is active at any point of time, even when the members are standard-layout structs that share a common initial sequence.

All that the standard says is:
In a standard-layout union with an active member of struct type T1 , it is permitted to read a non-static data member m of another union member of struct type T2 provided m is part of the common initial sequence of T1 and T2; the behavior is as if the corresponding member of T1 were nominated.

Here, only the object of type T1 is the active member. And pedantically, only a read access of the inactive member is expressly permitted.

My understanding is that the SDL_Event case is covered under the pointer-interconvertibility feature of the language.
6.9.2/4
Two objects a and b are pointer-interconvertible if:
(4.1) they are the same object, or
(4.2) one is a union object and the other is a non-static data member of that object or
(4.3) one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object, or
(4.4) there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.

If two objects are pointer-interconvertible, then they have the same address ...
http://eel.is/c++draft/basic.compound

[Emphasis added]
thanks guys =)
Topic archived. No new replies allowed.