Episode 17 — Types (Part 9: Practical Examples)
Welcome back!
Today we are looking at yet another derived type, similar to struct
but with a crucial difference: the way it handles storage. Let’s get started with unions.
Union types
In the previous episode, we looked at how each member of a struct
would be stored after the previous one, or in the same address as the whole struct
itself in case of the first (or only) member. This is not the case with unions, as its members’ storage will overlap. A union is thus defined as a type consisting of a sequence of members whose storage overlaps. This means that, at most, the value of only one member can be stored in a union at any one time.
At first glance, this may seem as an unnecessary complication or a waste, but then it becomes clear how unions are a space-optimised variant of structures, where an infinite amount of flexibility can be planned at declaration-time, with only what is truly needed being used at definition-time. In a musical context, this brings to my mind the idea of a player who has sent to memory several pieces but can only perform one of them at a time, accessing them when required.
Declaration and syntax
A union’s syntax is very similar to the one used with structures, as it uses the union
keyword, followed by an optional list of attributes, then by an optional name, and a struct declaration list between braces { }
. The reason the documentation uses the same terminology for unions’ and structures’ members is that, under the hood, unions, and structures are the same thing, just with a different way of allocating and managing storage. As usual, declaration and definition can happen on the same line of code, but it is not necessary.
How does it work?
While struct
create different variables in a set to be accessed and used later, union
is basically shared memory. The process of creating a union
allocates enough memory to hold the largest member, but it can hold only one value at a time. That said, it becomes clear that we should not create unions by themselves, rather we would put them inside a struct
.
If we create an elementary union
containing a few elements:
union Tunings {
int us_A; // 445 Hz
int eu_A; // 442 Hz
int verdi_A; // 435 Hz
int baroque_A; // 415 Hz
int another_A; // 392 Hz
};
This gives us some possibilities of tuning when performing classical music. A
represents the note A (La in mainland Europe language), while the part before the underscore in the name provides a description. us_A
shows what many US orchestras use for their tuning, eu_A
the one used mainly in Europe and Asia, verdi_A
the proposition the Giuseppe Verdi brought in front of the Italian parliament but didn’t pass, baroque_A
one of the more common tuning used by period instruments, and another_A
just another option. To show you how unions work, we can create a union object based on this one:
union Tunings tunings;
We then assign some frequencies to its members, using dot notation:
tunings.baroque_A = 415;
tunings.eu_A = 442;
Now, imagining that we planned for a modern concert, and then we had a change of plan with tonight’s concert being played on gut strings tuned at 415 Hz, we may want to announce the fact:
printf("Switching to baroque tuning: A = %d\n", tunings.baroque_A);
To our amazement, the output is:
Switching to baroque tuning: A = 442
Why is that? Following the definition of unions, only the last assignment is actually stored, and being all members of this union integers, they are actually getting the same value overwritten each time. Even checking the value of other members returns the same value:
printf("Checking other tunings: A = %d\n", tunings.eu_A);
printf("Checking other tunings: A = %d\n", tunings.us_A);
printf("Checking other tunings: A = %d\n", tunings.verdi_A);
printf("Checking other tunings: A = %d\n", tunings.another_A);
The output is always 442
. This example may not be the best possible one as we only have integers as members, but it gives you a good idea of what is happening. A more generic example would be to have different types inside the union, and see how trying to access, say, an integer member after having defined the floating-point one, would result in undefined behaviour.
So, when are unions useful? As said before, they are optimal inside struct
s as they provide options among which only one can be chosen eventually. For example, a struct
describing a musician, with an age
integer member, and a float
describing their height, would benefit from containing a union
called FavoriteTuning
with several options inside, among which only one could be chosen:
struct Musician {
int age;
float heigth;
union FavoriteTuning {
int myIntT;
float myFloatT;
double myDoubleT;
} ft;
};
See how, during the building phase of the structure, we also need to declare the union and give it a name (ft
). Creating a musician object would then require them to pick between an integer, float
, and double
tuning, as only one could be stored. Look at what happens in this case:
struct Musician benjamin;
benjamin.age = 42;
benjamin.heigth = 1.71;
benjamin.ft.myIntT = 442;
benjamin.ft.myDoubleT = 442.5;
printf("Benjamin's age is %d, his heigth is %.2f, his favourite tuning is %d.\n", benjamin.age, benjamin.heigth, benjamin.ft.myIntT);
The output for the tuning would be 0
because in initialising the .myDoubleT
member, we overlapped the .myIntT
storage space, effectively making it inaccessible.
What’s next?
I sincerely hope you liked this short episode on unions. The deep dive we took with structures helped us keep this compact. Next time, we are going to look at functions, possibly briefly as well, as the concept itself is pretty simple. Until the next one, thank you!
Bottom Line
Thank you for reading today’s article.
If you have any question or suggestion, please leave a comment below or contact me using the dedicated contact form. Assuming you do not already do so, please subscribe to my newsletter on Gumroad, to receive exclusive discounts and free products.
I hope you found this article helpful, if you did, please like it and share it with your friends and peers. Don’t forget to follow me on this blog and to let me know what you think.
If you are interested in my music engraving services and publications don’t forget to visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).
You can also support me by buying Paul Hudson’s Swift programming books from this Affiliate Link or BigMountainStudio’s books from this Affiliate Link.
Thank you so much for reading!
Until the next one, this is Michele, the Music Designer.