Learning the C Programming Language as a Classical Musician [10]

Episode 10 – Types (Part 2: Compatible & Composite Types)

Welcome back!

First of all I want to apologise for the complexity of last episode. It was really hard for me as well to put together, and the topic, when faced for the first time, is a true punch in the nose. We are all used to types and categories in our everyday life, we may still even have a few memories from our philosophy classes on Immanuel Kant’s Critique of the Pure Reason, where categories of classification made their first appearance. One of the most interesting aspects for me has been the fact that “nothingness” has indeed its own type in programming because, well, “nothing” cannot exist in a numerical universe. Take mathematics: zero represents the absence of value, and yet it is not a blank spot in our minds or on our papers, it is a vertical ellipse of ink, something tangible and real.

But let us not indulge further into Romanticism, and tackle the next chunk of our journey into C types: let me introduce you to compatible and composite types.

Let’s get started.

Compatible types

The first thing we need to get out of the door is the concept of translation unit. Once more it has a scary name but, in short, it is just a file where your code is written. For example, the main.c file in your Xcode project is a single translation unit. If you create a new file to add functionality to your program, for example instruments.c, that file is another translation unit. Now that the basic concept is clear, let’s get a bit deeper. In Episode 5, we looked at the different phases of translations, the process during which your code becomes an executable program. At a certain point during such process, your source file is passed through the preprocessor, meaning that its header files (those included with the #include directive) are expanded and literally included, sections of code which are optional due to #ifndef tokens (we will get there!) are included (or not), and macros1 have been expanded.

Now for the important part: in a C program, declarations (i.e., fragments of code ending with ;) referring to the same object or function in separate translation units do not have to use the same type, rather sufficiently similar types, known as compatible types. This is possibly an unnecessary complication, since languages such as C++ do not use this separation: two types are either the same or they are not. Let us specify that when we say “referring to the same object”, we often mean different instances of the same structure, not necessarily the same integer variable accessed from two different files. Being compatible has the obvious advantage of allowing you to manipulate different objects together.

Two types, for generic purposes we will use the letters T and U, both uppercase, are named compatible if any of the following conditions apply:

  • they are the same exact type
  • they are both pointer types, and they point to types which are already compatible
  • they are array types and their elements are compatible (for example, two arrays of integers to store the frequency of some pitches). If they are constant in size, they must have the same size to be compatible.
  • they are both structures, unions, or enumerations, and they have the same tag (for example, they are both struct). Moreover, if their declaration is already complete, they need to have the same amount of members, all of which declared with compatible types, and have matching names. In short, if they are the same, it is easier for you!
    • If the two types are enumerations, their raw value of the members (the integer hidden inside each member) must be the same.
    • If the two types are structures, the corresponding members must be declared in the same order.
  • they are function types and their return types are compatible, for example both functions return integers. If they have parameters, the number of parameters must be the same.

To make an example, the character type char is incompatible with either signed char and unsigned char. Should you be using two incompatible types, the compiler would throw an error for “undefined behaviour”. Once we delve into practical examples we will have a lot of time to dedicate to understanding how this work, but for now, just try to familiarise with the concept of two compatible types. If you feel it too nebulous, never mind, skip it for now, as said, we will come back to it.

Composite types

If you found the previous section complex and felt like skipping it, you may skip this as well, but I believe it worth a read. A composite type is a new type that is constructed from two types which are already deemed compatible. Beyond being compatible with both original types, it must satisfy a few conditions.

  • If types V and X are arrays, composite type W must pay attention to the size of the original arrays, as anything that is undefined is potentially dangerous. In short, if one array has a known constant size, the composite type is an array of that size. There are some arrays defined as of variable length, VLA, that, if their size is not clear enough, causes undefined behaviour in the compiler. It is better, to avoid issues, to either give a precise size to the array, or to leave it unknown, so that the composite array will also have an unknown size.
  • If one of V and X is a function type with a parameter list, then the composite type will inherit such parameter list.
  • If both V and X are function types with a parameter list, the type of each parameter in the composite parameter type list will be a composite type of the corresponding parameters (yes, you can breathe now!).

Once more, this is just a theory lesson, where we need to familiarise with the terminology. Without a practical example, and there are plenty of higher priority ones to be looked at before, it is normal that all this will make little to no sense. This is why I am not adding any musical connection in this lesson, as this is also very hard for me to grasp, and almost entirely new. Used to the cleaner Swift syntax, getting back to C is easy as long as we stay away from pointers and parentheses. For example, the documentation for composite types would propose something like this:

int f(int (*)(), double (*)[3]);

I am unfortunately unsure of how to properly read this, but let’s try: this is a function (not because of the f identifier, which is the only easy thing in this line), with a return type of int. This function accepts two parameters, another function which is also a pointer and returns an integer, and a pointer array of doubles capable of holding three elements. I hope you will agree that this stuff is really hard and that it is normal if our brain feels like melting when facing it.

What’s next?

I think we can call it a day for this episode, and, finally, in the next one, we will tackle some practical examples of each of the types we have encountered so far.

Thank you for your patience, and I hope you are still following me after these last two very complex episodes.

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.

  1. Macros are another kind of shortcut that let us define a value that we want to use in our program many times. It is similar in functionality to a file scope variable, and it is introduced with the #define directive.
    For example: #define notes = 12;

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

2 thoughts on “Learning the C Programming Language as a Classical Musician [10]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: