Learning the C Programming Language as a Classical Musician (9)

Episode 9 – Types (Part 1: Overview)

Welcome back!

Today we are going to take a deep dive into the concept of type, that is, how C interprets the data you pass to it. While for us it may be natural to separate a word from a number, an integer from a decimal, an abstract category from a description of an object, for computers things are not at all this automatic. To be clear, not even for us this is automatic, as it comes with experience and education, but at the time of the introduction of the C programming language, neural science and machine learning either didn’t exist or were in their infancy.

As my cello teacher used to say, we are very clever here (tapping his head), but we are not very clever here (tapping his forearm, sometimes right, sometimes left), instructions need to be simple, and it must be possible to replicate them a thousand times exactly the same. This is a very appropriate introduction to today’s topic, as it encourages us to approach classification and computer instructions as if we were explaining things to a child. As we will see, this child is way smarter than we may expect!

Let’s get started.

Types and their classification

In C, all objects, functions, and expressions (a sequence of operators and their operands, such as a+b) have a property called type, which determines how the binary value stored in the object or evaluated by the expression should be interpreted by the compiler.

Types are classified into four branches: the void type (basically C’s concept of nothing, as we cannot just say to a computer that nothing is simply nothing, it needs to have a definition), the basic types, the enumerated types, and the derived types.

We have encountered the void type in some code samples before, mainly as a return type for functions which perform some action but do not give back a value (and can be safely destroyed at the end of their lifetime) or, in function declarations, when we wanted to specify that a function did not accept any argument.

Basic types are themselves divided into four categories: the char type, representing a single character; signed integer types, that is integers where it matters whether they have a positive (> 0) or negative (< 0) value; unsigned integer types, akin to absolute values in math (for example, the absolute value of -5, written |-5|, is 5); and floating-point types, what we would call “decimal numbers” such as 10.52.

We saw what an enumerated type is when tackling the tag namespace in Episode 8, and, in this classification, they occupy their own slot. In simplest terms, they are a collection of integer values that are given a name to make them easier to read for us.

The last category is derived types, which includes arrays, structures, unions, functions, pointers, and atomic types. Arrays are an ordered collection of objects, such as:

int pitches[12] = {1,2,3,4,5,6,7,8,9,10,11,12};

Structures and unions are sequences of members grouped under a singe banner, with the difference that, in structures, the storage is allocated in an ordered sequence, while in unions, storage of members overlaps (we will come back to this). We already know what functions are, and their type depends on whether they have a return type or not, and whether they accept parameters or not.

int g(void); // is of type int(void)
int h(int); // is of type int(int)
/**
 Read the first function as "a function named g that accepts no parameters and returns an integer.
 Read the second function as "a function named h that accept a single integer parameter and returns an integer.
 */

Pointers are possibly one of the most interesting features of the C programming language and of its derivatives, in which it is a type of object that refers to a function or an object of another type. They are a complex topic that requires a lot of time to be digested but, in short, a pointer is an object that is storing the memory address of the object it is pointing at.

Atomic types, introduced in the C11 version of the language, are types that are free from what is called “the data race”, that is, the order in which instructions are executed. CPUs have cores and threads, and each operation needs to be assigned to a thread. If two or more operations are assigned to the same thread at the same time, we get an error, unless the operation involves an atomic type. This definition is oversimplified, but for now, it will do.

Type groups

Beside dividing types into categories, we can also divide them into groups, namely eight of them. Object types are all types that aren’t function types, a quite broad grouping that works as a catch-all group. Character types are either of char, signed char, and unsigned char; you recall that characters are a visual representation of certain specific number mappings, so it may be useful sometimes to specify whether they have a sign or not. When a number object is created, it is given the signed type as default, so a signed char can represent values between -128 and 127 in its 8-bit version, while assigning it the unsigned char type, makes it able to represent values between 0 and 255.

char c = 97;
printf("%c\n", c); // prints lowercase a

Integer types are anything that deals with whole numbers directly: char once more, as its mapping is indeed an integer, signed and unsigned integer types, and enumerated types. Trying to pass a negative number to an unsigned integer will give this:

unsigned int ui = -4;
printf("%u\n", ui); // prints 4294967292!

But why does it print so? Because, specifying the unsigned nature of this number, we asked to give back only positive numbers. The storage capacity of ui is 4 bytes, which accepts values from 0 to 4,294,967,295. Asking it to go below zero and to -4 makes it overflow to the other side, as if it had toured the globe, and so we have -1 = 4,294,967,295, -2 = 4,294,967,294, -3 = 4,294,967,293, and -4 = 4,294,967,292! I know this may seem difficult to grasp but let’s put ourselves in the place of the computer. It cannot give back something it doesn’t know, it cannot leave an answer blank, it needs to react, so if negative numbers are not accepted, and yet we are throwing them at it, then it must reply with something it knows.

Real types are those who could be represented in the set of real numbers, such as integers and floating types. This last type is subdivided into real floating types and many other types, which are more interesting to those who delve into complex math.

Arithmetic types are a broader group that, theoretically, should also include the real types as its subset, in which all integer types and all floating types end up. I have touched on the “floating” word too much without giving you a clarification: as you know, a decimal number can be finite or infinite, that is, the number of digits after the decimal point (or comma, in Italian) can be limited or unlimited. The concept of unlimited is extraneous to computers, so we need to use a concept known as precision, which allows us to choose how precise a decimal number is. Just to give you an example, float is less precise than double. In C we need to specify, when printing values out, how precise we want our decimal number to be because passing it to the printf function automatically converts it to a double but makes it default to 6 digits after the dot. We can add a number with the format specifier to tell the printf function that we want more precision, like this: %.8f will use 8 digits after the dots.

The last three groups are: scalar types, which is a joined set of arithmetic types with the addition of pointer types; aggregate types, which group together array and structures; and derived declarator types, which groups together array, function, and pointer types.

What’s next?

In the next episode we will keep exploring types, trying to see when two types are defined as compatible, what composite types are, and possibly a few other things. Then, possibly in a third episode, we will cover most if not all of these types with a few practical examples.

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.

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 (9)

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: