C is able to handle a useful – but limited – amount of data types, that makes possible to use complex values that the computer woudn’t understand.

As you probably know, computers can only understand sequences of bits, but we humans can use all sorts of data types.

Integers and characters are just abstract concepts for a machine, so it’s up to the compiler and the language to make them usable.

We’re about to see a few keywords, which are divided into two groups: data types, modifiers and qualifiers.

The combination of data types and modifiers determines the amount of space occupied in the memory, while qualifiers determines whether a variable can be changed or not.

Data types


A simple tree diagram that sums up all data types

Data types are the keywords that specifies the type of value to store and how that value has to be converted into bits or bytes.

We’ve already used some of them in this blog, such as int, char, arrays, pointers and user-defined data types.

Data types are grouped in different categories, as the table that follows explains:

Group Examples
Basic types int, float, double, char
Derived types arrays, pointers
User-defined data types structs, unions, enum, defined types (typedef)
Void type void

The common subdivision wants enum in its own category. while other user-defined data types in the derived types one.

Let’s see them in detail.

Basic types

They are the fundamental types, necessary to do all basic operations, in particular arithmetic ones.

In C they are four:

  • int
  • float
  • double
  • char

and they are divided in two groups:

Integral int and char
Floating point float and double

Integral types are able to handle only whole numbers, while floating point ones support also a fractional part.

INT

It’s an integral type supposed to contain whole numbers.

Despite being a basic type, int hasn’t a fixed size: it can either be 16 or 32 bits long (2 or 4 bytes), depending on the compiler.

You can force it to be one or the other using the modifiers short and long, that we will see in one of the next chapters – scroll down 😉

CHAR

It’s the type assigned to characters.

Now you might be asking: why is a character considered an integral type?

The answer is simple: even if many languages distinguish characters from integers, they’re both stored in the memory as whole numbers and since C allows you to manage the memory as you want, you can use char values both ways.

Each character is assigned to a number, using an encoding defined by the ASCII code. For example, you can assign a value to a char variable writing either 'A' or 65 – its corresponding ASCII decimal number – as follow:

– or –

The only difference between char and int is their size: the first takes just 1 byte – enough to store a character – the second takes from 2 to 4 bytes.

Do you want to know more about characters or strings? Read this!

FLOAT and DOUBLE

To represent floating-point numbers you can use two data types: float and double.

Double is more precise – and in maths and physics more precise means that it supports more digits after the point – but it takes the double of the space used by float: 8 bytes vs 4 bytes.

What’s the difference between float and double? An explanation by GeeksforGeeks!

Derived types

A derived type is formed using basic types.

They are:

  • Arrays
  • Pointers
Arrays

We talked about arrays in one of the previous posts: you can find it here.

To cut it short, an array is a set of variables of the same type – and so size.

For example, an array of 10 float elements, declared as follow:

will take up 4 bytes for each one of the 10 elements. Furthermore, the second element will be allocated into the memory after the first, the third after the second and so on.

Pointers

A pointer is a variable that contains the memory address of another variable and it’s derived from the type of the variable itself.

Example:

var is the variable itself and its value is 10.4, while p is a pointer to var and its value is a memory address, extracted using the & symbol.

void pointers can be converted in pointers of any type.

To get the value of the pointed variable, you can use the * symbol before the name of the pointer.

Curious about pointers? This article is for you!

User-defined data types

They are types that can contain one or more data of any category as specified by the programmer.

They’re similar to arrays, but the main difference is that they can hold values of different types.

There are four keyword to create user-defined data types:

  • typedef
  • struct
  • union
  • enum

I recommend to read this post to get a better understanding of user-defined data types.

TYPEDEF

This keyword allows you to give another name (ID) to an existing type.

Such a feature can become handy when you add qualifiers and modifiers – that I will explain later in this post – to a type and you want to shorten the resulting name.

The type of the following variable is quite long:

Therefore, if you want to define similar variables it’s better to shorten it:

STRUCT

A structure is a set of variables of different types.

In some situations arrays aren’t sufficient. For example if you want to register information about a lot of people it’s far better defining one array of a structure than creating one array for each bit of information.

It’s faster (and easier to understand) to write:

than:

UNION

A union allows two or more variables to share the same part of memory.

Any change to one variable will affect the others too.

Why should I use them? You might ask.

For easy conversions, for input fields that have to accept different values depending on previous inputs and cryptography.

In the previous post, I used unions to make an easy cryptographer and to save either the user’s nationality or the user’s city of birth depending on a previous answer.

ENUM

An enumeration is a type that contains integer values, whose values have custom names.

Given these two variables:

and these assignments:

the final values are identical.

The only advantage of using enumerations is that they make the code clearer to you.

VOID

The void data type has two functions:

  • It’s used as return type of a function that doesn’t return a value.
  • It’s used to define a generic pointer that can point to any type of variable.

The pointer int *p can only hold the address of an int variable, while void *v can point to an int, a char or any other type.

Modifiers


Modifiers are keywords put before the data type ID, which change the interpretation and memory size of a variable.

We divide them in two independent groups based on their function.

Two keywords from the same group can’t be used together.

Size modifiers Sign modifiers
long signed
short unsigned
long long

If you don’t specify any modifiers, the compiler will use a default one, which may vary depending on the compiler.

The default ones are usually long and unsigned.

Let’s see them in detail.

Size modifiers

You can use the short modifier only with int and it changes its size from 4 to 2 bytes.

You can use the long modifier before int, to change its size to 4 bytes – in case the default one was different -, and double, to increase its size from 8 to 12 bytes.

The long long modifier can only be used with int to set its size to 8 bytes.

Sign modifiers

These are clearer to understand.

You can apply them only to int and char.

A signed variable can hold both negative and positive numbers, while an unsigned variable can only hold positive numbers.

If a variable is unsigned, it has more space for greater numbers, because it isn’t necessary to save the sign of its value.

Qualifiers


Qualifiers are keywords that modify the program permissions to access a variable.

They are three:

  • const
  • volatile
  • restrict

CONST

Put before the type ID – or the modifier if present – of a variable, it sets it constant, therefore you can only initialise it, but you can’t change its value.

VOLATILE

Put before the type ID – or the modifier if present – of a variable, it allows an external program – or process – to change its value.

RESTRICT

You can only use restrict with pointers and you have to insert it between the asterisk and the variable name.

It tells the compiler that the qualified pointer is the only way to modify the variable pointed by it and it’s only used for optimisations.

#define and sizeof


Let’s see two other useful keywords: #define – a macro – and sizeof – an operator.

In short, a macro is similar to a function, but it’s translated during compilation.

What is the difference between a macro and a function?!

Another way to define a constant is to use the #define macro, which syntax is:

Note: don’t put a semicolon after it.

Where:

  • name is a custom name given to the constant
  • value is – you never would have guessed it – its value

The difference between a defined constant and a constant variable is how they are compiled.

A constant variable, like any other variable, will occupy a certain amount of RAM based on its type size, while a defined constant is replaced with its value during compilation.

Basically, using #define is like using the “find and replace” option of most text editors, but it’s an automatic process.

The sizeof operator returns the amount of space reserved to a variable: basically, the size of its data type.

Use it like this:

For example:

in

the value of b will be 4 – or even 2, depending on the compiler and on the machine.

Conclusion


This post is a good summary of all the data types of C, but most importantly it introduces you to modifiers and qualifiers.

These are useful not only to optimise the memory use of your program and to change the maximum value that a variable can hold, but also to make your code compatible with other machines, since they might use other default modifiers and qualifiers.

Has it been all clear? If not, feel free to ask me anything through a comment.

Otherwise, if you think that this may help some friends of yours, share it!

Anyway, the post is finished, have a good day and we will see next week!

From Zephyro it’s all, Bye!

Categories: Learn C

Leave a Reply

%d bloggers like this: