Learning the C Programming Language as a Classical Musician

Episode 16 — Types (Part 8: Practical Examples)

Welcome back!

Finally, after a much longer hiatus I could have ever forecasted, I am finally able to get back to learning, and to writing, sharing all these things with you. Today we are continuing our journey through derived types which, after last lesson’s arrays, await us with structures. Fasten your seatbelts, then, as we are ready to jump into a most fascinating topic!

Structure types

When, in the last episode, we talked about arrays, we noticed that they were the first type we encountered that could contain more than a single element inside itself. We were limited to the fact that all elements needed to be of the same type, but this was the only real limitation. Structures go a step further, as they are a container for multiple objects of possibly different types, even arrays! While arrays could be compared to an ordered sequence of objects such as characters in a word, like:

char lvb[9] = "Beethoven"; 

Structures are much bigger objects, which allow us to store different aspects of what we are setting out to describe1:

struct composer { int age; double heigth; char name[]; }

Notice how we have not specified the size of the array of characters that will define our composer’s name because we know this is going to change between every instance of our struct. To do so and not incur into a compiling error, we have been obliged to put the array at the end of the members list because structures allocate memory in contiguous spaces, so having a variable amount in the middle would potentially create havoc in our system.

Once we have declared our structure, we can create our first instance of it:

struct composer Beethoven = {
    57,
    1.65,
    { "Ludwig van Beethoven" }
};

Wonderful, right? Not quite, as Xcode would warn us of the following:

So, what is going on? Let’s find out, starting from the beginning.

Declaration and syntax

A structure is a type consisting of a sequence of members whose storage is allocated in an ordered sequence (and in this, they are different from unions, whose members’ storage overlaps). The type specifier for structures is identical to the one used for unions, with the only exception of the keyword used: struct vs union. They start with the struct keyword, followed by the name we want to give it and, between braces, a struct-declaration-list, which consists of any number of variable declarations, bit fields, and static assert declarations2, separated by commas. This list cannot contain members of an incomplete type or of a function type, except for a flexible-sized array (which is precisely what we were attempting to use above). In C23, an optional list of attributes will become available.

Explanation

A fundamental concept in C is the address in memory of a specific object, such as the position of the bow on a cello’s string is crucial for the production of a specific sound quality. If we set out to print the address in memory of a struct and of each one of its objects, we will see that its address and the address of its first member coincide. To be able to work with the above example, I have changed the size of the composer.name array to [20].

printf("The address of the struct Beethoven is %p\nThe address of its 'age' member is %p,\n\tof its 'heigth' member is %p,\n\tand of its 'name' member is %p.\n", &Beethoven, &Beethoven.age, &Beethoven.heigth, &Beethoven.name);

The %p conversion specifier allows us to store a pointer to a specific address, while the “address of” & operator extracts the address from an object. Notice how we are using dot syntax to access the members of the struct. Beethoven.age means we are trying to access the member age of the object Beethoven. Luckily, Xcode is very helpful in showing us the available alternatives as soon as we type the . after the structure name. Here is the output:

The address of the struct Beethoven is 0x7ff7bfeff1e0
The address of its 'age' member is 0x7ff7bfeff1e0,
	of its 'heigth' member is 0x7ff7bfeff1e8,
	and of its 'name' member is 0x7ff7bfeff1f0.

Notice how the address of the structure itself and of its age member are the same. Eight bytes later we find the start of double heigth, and a further 8 bytes later we find the start of the name character array. Using the sizeof function, we can inquire what size each of our members is:

printf("Size of age: %lu;\nSize of heigth: %lu;\nSize of name: %lu\n", sizeof(Beethoven.age), sizeof(Beethoven.heigth), sizeof(Beethoven.name));

%lu is “long unsigned integer” as a size will always be positive, so storing information about its sign would be a waste of resources. Here is output:

Size of age: 4;
Size of heigth: 8;
Size of name: 20

Interesting: age is of size 4, yet it occupies 8 bytes. In reality, it does not: if you recall the lesson of integers, we saw how each int object is guaranteed to be allocated at least 8 bytes. In this case, we are using only 4, and the rest is padding between members, which can happen in any place apart from before the first member. Theoretically, for this specific number we could have used even less than that, but the compiler had no way of knowing this beforehand. In summary, the size of a struct is at least as large as the sum of the sizes of its members, in this case, at least 32 bytes.

Still, all of this does not explain why we are encountering issues with the array of flexible size as the last member of our composer struct. We know that, if a structure defines at least one named member, it is additionally allowed to declare its last member as an incomplete array type (that is, an array with undefined length). When we attempt to access it, though, something strange happens: as the compiler needs to allocate a fixed amount of space for objects, it has to know how much space is needed for the whole structure, included the final array. This means that, if we do nothing, the compiler assumes that such array will hold a single element, and allocate the appropriate size for that element, in our case 1 byte for 1 character. To be able to use this feature, we need to allocate the required space manually every time we create a new structure. Let’s assume we want to create an object for the composer “Ludwig van Beethoven”: we have to access the address of the struct we are creating and allocate enough memory to store its name member. Currently, the structure is 4 plus 8 plus 1 bytes long, that is 13 bytes, and, to store the string “Ludwig van Beethoven”, we need to add 19 more bytes. To realise this, we must import a new header file in our document, adding:

#include <stdlib.h>

Just below the <stdio.h>. This will give us access to the memory allocation function called malloc(size_t size), which will reserve a specific amount of space for the object passed as argument. Now create a new structure very similar to the one we created before but with the flexible array as last member:

struct mComp { int age; double heigth; char name[]; };

At this point, allocate enough memory for the name of the composer you want to create, using the * dereference operator to access its address at creation:

struct mComp *mBeethoven = malloc(sizeof(struct mComp) + 20); 

Now, this next step required a good 30 minutes of head scratching as I was used to how simple struct creation and management is in Swift, yet I recognise that all the hard work done here in C will bear juicy fruits in the future. It seems that, for this example, I had chosen a too complex or straight impossible thing to do, which was an incomplete array type of characters. The reason this was not good is not entirely clear to me, but I am aware that character and string manipulation were in their infancy when C was developed, and that a clear method of handling them as well as in Swift would not come for a long while. I was therefore obliged to change the definition of the mComp structure so that its last member read char *name. As we will see, arrays and pointers are just the same thing, but, for now, this allowed me to bypass the issue with character arrays of undefined length. If you are curious, you can check the official documentation for C, where an example of a struct with an array of double as last member is given, and how that can indeed work. Once performed this change, I had wished that, having allocated memory, I could just initialise the whole structure, which was just not the case. Since we had used the * pointer operator, we could not use dot syntax to access the structure’s members, rather we needed the arrow operator -> (a dash and a greater than symbol) instead:

mBeethoven->age = 57;
mBeethoven->heigth = 1.65;
mBeethoven->name = "Ludwig van Beethoven";

Now, printing the result was straightforward, including the %s conversion specifier for strings:

printf("Beethoven's age is %d;\n\tHis heigth is %.2f;\n\tHis name is %s!\n", mBeethoven->age, mBeethoven->heigth, mBeethoven->name);

Which output to:

Beethoven's age is 57;
	His heigth is 1.65;
	His name is Ludwig van Beethoven!

You can hopefully see the deep power of structures, with almost endless expansion possibilities. As we will see with unions, it is possible for a struct to have unnamed members; if any of those members is a struct itself, it will be known as an anonymous struct. Every member of an anonymous struct is considered to be a member of the enclosing struct of union. Thus, to access members of an anonymous struct, we will use dot syntax up to the enclosing struct or union. Here is an example, taken from the documentation:

 struct v {
    union { // anonymous union
        struct { int i, j; }; // anonymous struct
        struct { long k, l; } w; // struct with two members initialised a w
    };
    int m;
} v1;
    
v1.i = 2;
v1.k = 3; // Error: No member named 'k' in 'struct v'
v1.w.k = 5;

Here is a musical example, dedicated to Russian composer Dmitrij Šostakovič (1906-1975):

struct compositions {
    union {
        struct { int quartets, trios, duos; } chamberWorks;
        struct { int cantatas, oratorios, missas; } choralWorks;
        struct { int symphonies, symphonicPoems, concertos; } orchestralWorks;
    };
    int total;
};
    
struct compositions ShostaComp;
ShostaComp.chamberWorks.quartets = 15;

It is also possible to declare a struct without defining it, resulting in an incomplete type. This would be particularly useful for structures that will contain other structures, referring to each other using pointers, for example:

struct y;
struct x { struct y *p; /* ... */ }; 
struct y { struct x *q; /* ... */ };

The first line declares a struct named y without defining it. The second line declares a new struct named x, and defined as containing at least one member in the form of a struct of type y, named p and being a pointer as well, meaning that y’s address will be accessed in several places at the same time. The third line finally defines the members of the struct y, as containing a struct of type x, named q and referencing x’s address. You can easily see how intertwined this becomes. How this can be useful in practical context will be hopefully covered in a future episode!

It is also possible to just introduce a new struct with the name of the type (e.g., struct s) inside another declaration. To understand all the implications of this, I suggest you review the episode on scope.

As a closing note for this episode, I would like to stress out how important for a struct not to have a member of its own type because a) members of incomplete types are not allowed, and b) a struct type is not complete until the end of its definition. A pointer to it would be allowed, though, but we will cover it much later. Finally, remember that since a struct declaration does not establish scope, any nested type, enumeration, or enumerator introduced within a struct is visible and accessible in the surrounding scope where the struct has been defined.

What’s next?

In the next episode, we will look at unions, which are a peculiar type that I had never encountered during my Swift travels. Until the next one!

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. The following code intentionally contains a mistake, which will become clearer later in the episode.
  2. Bit fields and static asserts will be covered much, much later, and are not fundamental for the understanding of structures at this point of our journey.

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

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

Create your website with WordPress.com
Get started
%d bloggers like this: