Completed: / exercises

The basic blocks of C programming

As was promised before, the course proceeds in about the same order as the Elementary Programming course. At least as much as it is possible with C. This time we will refine two basic pieces of programming:
variables
and
functions
. Without them it is rather difficult to do any programming, especially in C, which does not even have a main program.

A frame in the shape of C

Learning objectives: After this portion you will know how to write a C program that can be compiled and executed.

Compulsory symbols

Unlike in Python, where a code file may contain just a single line of code that does something, C requires certain basic things. At the end of the previous piece of material there was an example of a very simple C program:
#include <stdio.h>

int main() {
    printf("aasisvengaa!\n");
    return 0;
}
The actual code to be executed still is only one line, but around it has appeared this and that. We got a preparatory look at these in the previous material. The #-symbol at the beginning does not, by the way, make a line a
comment
(// does!). All the lines that begin with # are instead lines meant for the
preprocessor
to handle. In short, a preprocessor is a part of the
compiler
that processes the code file according to the
directives
, the lines marked with the #-symbol at their beginning. At this point we will only handle the directive called include, which resembles Python's
import command
. Unlike Python, C does not provide a built-in print function. The printf function belongs to the stdio
library
(Standard Input/Output). To use printf, the program includes the stdio.h header file, which provides the compiler with the information it needs to recognize and correctly use the function.
The name ends with the .h, which is short for header. This .h-file is a library's
header file
, which includes the introductions of the functions, constants and possible variables of the library. The actual code of the library is in the stdio.c file. We usually will not be seeing it, since compilers typically deliver the standard libraries as compiled binary files. There will be more about header files later on the course. While perusing the code you might notice one difference to the import of Python: when printf function is called, it is not written stdio.printf, just printf. In C include works actually like Python's from moduuli import * and separate namespaces are not used. This means you need to be more careful with naming your functions than usually.
The library used here, stdio, includes functions that are related to printing and reading input. At this point it will be used in practically every program. Other at this point or soon relevant libraries are at least math and stdlib. When you need these they will be introduced to you so that you know when and how to use them. C does not have a similar friendly documentation base as Python, so we recommend you either use a C book or the ”UTFG” method (just google everything).
Another notable difference is that you can not make a C program without
functions
. The main program like in Python does not exist. Instead, C programs have a
main function
named main, where the execution of the program begins. In this course, the return type of main is int. This return value, provide information on how the function ended. This value can be useful, for example, in scripts that execute several programs in sequence and use the return value to determine, among other things, whether the program was executed successfully. We will go deeper within the inner workings of a C function in this material, but not yet. At this point it is enough that you get what this mysterious int main() {-line is. It defines the main function, which does not have
parameters
. At the end of the line the curved bracket is the beginning of a statement block. All the things between this and the closing curly bracket are inside the function.

Variables' metamorphosis

Learning objectives: After this portion you will have mastered the basics of the variables in C. This includes understanding and defining the types of variables, and some sort of understanding in which way variables reside within the memory of a computer. We will also take a look at how variables can be printed and how their types can be changed.

Refreshing memories

Just a reminder about what was said about the
variables of Python
in Elementary Programming before we delve into the inner workings of
C's variables
. The basic idea was to connect the name appearing in the program to the value inside the computer's
memory
. This familiar animation might work as a fine refresher:
So, that was Python, about which was nagged how a
variable
is a reference to a
value
and does not actually contain a value. This is demonstrated in the animation at y = x, where both of the variables point at the same value. It was possible to show this even in the Python interpreter using id function:
>>> a = 5
>>> id(a)
10894208
>>> b = a
>>> id(b)
10894208
Here can be seen how a and b are not identical only by their value, they are the same
object
. This does not have much to do with the basic types of variables, like numbers and strings, because these
data types
are
immutable
in Python. With
lists
you could instead see actual repercussions:
>>> a = [1, 2, 3]
>>> b = a
>>> b.append(4)
>>> a
[1, 2, 3, 4]
This behaviour had at least one typical application: the functions were given lists that the function edited but did not return. They were not returned, because that was not necessary - the edited list was the same list the function had been given. As an example of this, we can use the add function from the collection program example:
def add(collection):
    print("Enter the details of the album to add. Leave the album name empty to stop.")
    while True:
        album = input("Album name: ")
        if not album:
            break
            
        collection.append({
            "artist": input("Artist name: "), 
            "album": album, 
            "n": ask_number("Number of tracks: "),
            "duration": ask_time("Duration: "),
            "year": ask_number("Release year: ")
        })

Comparing memories

In C
variables
are tied to their values in the sence that you could say the variable contains a value. The variable is a name the programmer has set for the area of the
memory
the value is located in. When a new variable is created, a new area of memory is reserved for it. Let us return back to this after a moment, but let us watch an animation about it before we continue.
Another way C's variables clearly differ from Python's is that they have a separately defined
type
. The type is specified in the variable's
declaration
, which has the form variable_type variable_name.
Unlike in Python, C variables must be declared before they can be used. In the simple cases used on this course (we will see almost at the end of Module 1 some exceptions), declaring a variable also
defines
it, meaning that storage is reserved for the variable. A value can also be given when the variable is defined. This is called
initialization
.
For example:
int i;
int x = 5;
Both lines declare and define a variable. The second line also initializes x with the value 5.
What happens if you do not initialize a variable? Usually you get very unpredictable results, which in the worst case can also pose a security risk.

Types everywhere

In C the types of the variables play a bigger part than in Python. C has more basic types. Above you can see an
integer
type int. Other integer types of C are short and long. These types also have modifiers signed or unsigned. signed is the default for the integer types, meaning that the variable can represent both negative and positive values. An unsigned integer cannot represent negative values, but it can represent a larger range of non-negative values using the same number of bits. For characters there is the variable type char, which is actually an integer type, and that will be explained later. Unlike in Python, in C there is no
string
of characters variable type. We will later return to how to handle strings.
Also
floating point numbers
exist in C, and they work in a similar manner as they do in Python. Not surprisingly, also they are just bits in the memory, they are just interpreted in a different manner. In C there are two float types, float and double. It is not relevant to this course to understand how floating point numbers act within memory, but it is good to know that many values cannot be represented exactly and are therefore stored as approximations. We will return to variables and floating point number systems later.
Now that we know something about variable types, we can take on some exercises so that we will remember something about them later.
These exercises have no effect on course grading.

Defining variables - A presentation about variables

Let's begin from the simplest ones. Your job is to introduce three variables:
  • unsigned small integer N
  • floating point number keskipituus
  • character luokitus
Write the introductions in the box below.
Warning: You have not logged in. You cannot answer.

Defining variables - At the beginning there was...

Let's continue the exercises. Now your job is to introduce and initialize variables. This is the most recommended way of introduce variables, because it ensures that all the variables have some sort of sensible value. The required variables are:
  • unsigned big integer "counter" with initial value 0
  • integer "sum" with initial value 0
Write the definig lines in the box below.
Warning: You have not logged in. You cannot answer.

Types in print out

Among all the other fun things, the
types
of
variables
are also related to printing. When using printf, the format string tells the function how each argument should be interpreted and displayed. This means that a value of the same type can be displayed in different ways. For example, the integer 12 can be printed as 12, +12, 012, or 0xc. At this point you only need to know that printf receives both the values to print and a description of how they should be printed. There's an example of this below.
printf("%d\n", x);
In short (we will get back to this later), in the printf call above the value of the variable x is printed on screen as an integer and right after it a newline character. The first parameter of the function is a string, which says how to print, in this case "%d\n". Now %+letter characters are the same as the
placeholders
in Python's
format
method and after that the newline character \n. Actually Python does also have similar syntax - format is only a bit more modern and versatile way of doing the same thing. In the place of every placeholder in the
printf
function there will be one of the
arguments
after the string in the given order. In the example in the place of %d will be the value of the variable x in the print out. Unlike in Python, where you could use an empty place holder like "You gave a number {}".format(luku), in C each placeholder is tied to one or more types. Here is a list of the most commonly used ones on this course:
placeholder variable type
%d int
%i int
%hi short
%li long
%u unsigned int
%hu unsigned short
%lu unsigned long
%f float, double
%c char
%s char[]
In addition to these, there are other placeholders that display values using a different representation. For example:
placeholder meaning
%x prints the value of an unsigned integer in hexadecimal format
These exercises have no effect on course grading.

Class C of the Basic Printing School

Printing is not actually the most important thing on this course, but it is good to be able to handle the basics of it also. In this exercise you mainly practice choosing the right
placeholder
based on the type of the variable. The
compiler
usually returns
warning messages
about incorrect placeholders, but it still compiles the program through since it can be executed - the results just may be unexpected.
Your task is to pick the piece of code below and add print outs so that each defined variable's contents are printed on their own line. Unlike Python's printf function, C's printf function does not automatically print line break at the end, so you have to add it there yourself - you do not need to add anything else to the print outs.
HUOM: Check that the last row includes also a new line character
tkj-tulostusperuskoulu-c
int main() {
    unsigned long n = 4000;
    unsigned short laskuri = 0;
    char luokka = 'C';
    float keskiarvo = 0.0;
}

Return the code file below. The code has to compile without warnings.
Warning: You have not logged in. You cannot answer.

Functions' metamorphosis

Learning objectives: In this portion we will handle how to define functions in C and what differences they have with Python functions (typed parameters and return values). Prototype is introduced as a new concept.

Danger: Functions devour types!

On the base level defining and using
C function
do not differ from defining and using
Python function
. Function is defined on its own definition line, on which is presented the function's name and
parameters
, and the code of the function follows. The syntax is somewhat different: on the definition line there is no separate keyword like Python's def. Instead of it the definition of the function looks similar to a variable's definition - the brackets after the name tell us that it is a function instead of a variable:
int main() {
    return 0;
}
Instead of the def keyword, the function definition starts with a
type
. This type defines the type of the function's
return value
. The main function's retun value type is int, because it should return a status code of whether the function's execution went well or not. At the end of the function should usually be return 0;, which says that everything went well. In an error situation the return value is some other number, but we do not need to know much about them yet - right now the most important thing is to remember that there should be that return line at the end of the main function. In the same way the parameters should be defined to have a type:
float  calculate_distance(float speed, float time) {
    return speed * time;
}
If the arguments in the function call are of the wrong type, the
compiler
will return an error message (well, this is not completely accurate, because some types can be implicitly converted to other types, but let's not go into that yet). A function also has to return a value that is of the type defined for its return value. Otherwise you get another compilation error (again, this is not completely accurate, because some types can be implicitly converted to other types). A function that is not meant to return anything can be defined to have the return type void:
void print_instructions() {
    printf("This program does not do anything useful.");
}
The most notable functional difference between C functions and Python functions is that C functions can have only one return value. If you want to return more than one value, you need to use references (we will call them
pointers
) or the variables can be packed in a
data structure
. We will get back to this later.

Prototypes

In C also functions have to be introduced before using them.
Prototype
is function's introduction and tells the
compiler
that the program should include a function with this name that takes these types of variables as parameters and returns that type of value.
Prototypes sort of form a table of contents of the functions that appear in the program. If the C program consists of several different code modules, each of the shared functions has to be introduced in their header files so that the table of contents would include all of them. The header file provides the information that other code modules need in order to use these functions. The contents of the header file are included with the #include directive, which is handled by the preprocessor. You will learn more about this later.
Naturally, you can write the whole function before it is used. In that case, the function definition also provides its declaration. However, if the function is used from another source file, its declaration should normally be placed in a header file that the other source file includes.
The defining line for the prototype is identical to the actual function's defining line:
int calculate_sum(int a, int b);

int calculate_sum(int a, int b) {
    return a + b;
}
Usually adding prototypes to the code does not require anything else besides copying the definitions of the functions to the beginning of the code (below the
preprocessor's
instructions). Prototypes can also be located in a separate
header file
, which will be explained more thoroughly later. Using prototypes on this course is not optional - the evaluator's compiler has been instructed to produce an
error message
if prototypes are missing.
Note that the main function does not require a prototype, but it has to be named main.
These exercises have no effect on course grading.

Conical function

Let's write a little function as a warm up exercise. May the function be called cone_volume. As input it takes two floating point parameters, the cone's bottom radius and the maximum height of the cone. The function calculates the volume of the cone and returns it as a floating point number. You can use the primary school method for calculating the exponentiation (r*r). Use the double data type for presenting the floating point numbers!
Hint. Include math-library (math.h) into your source code to get the value of pi as the constant M_PI.
Write a code file where only this function is defined, without the main function. Remember the prototype!
Warning: You have not logged in. You cannot answer.

Basics of the C compiler

Learning objectives: In this section you will learn the basics of using a C compiler, at least enough for this C programming part of the course. This mainly means becoming familiar with a few of the most important compiler flags and learning how to read the error and warning messages produced by the compiler.
During the course we will use a virtual machine on the workstations where the compiler environment is already installed, but on your own computer you will need to install it yourself. You can find a link to the installation instructions on the course front page.

Basics of compiler magic

Earlier we introduced the following basic spell:
C:\path\to\somewhere>gcc -Wall -o myprogram.exe mycode.c
This command has altogether 4 parts, as was briefly explained in the first material. gcc is the name of the program being used., in this case the
compiler
(GNU Compiler Collection). The code file or file(s) to be compiled are found at the end. They are not preceded by an
option flag
beginning with a hyphen. Of these flags, -o defines the name of the file where the compiled code is stored. In Windows the name is given the .exe extension, while in Linux executable files normally have no extension. With the last flag, -Wall, we have actually cheated a little: it is not strictly needed to compile the program. It increases the number of
warnings
produced by the compiler by including several questionable and easily fixable constructs among the things it warns about. We have included the flag in every basic example because its use is highly recommended. Actually almost mandatory on this course, because the evaluator's compiler uses it and rejects code that produces warnings.
The order of these parts does not matter, as long as -o and the program name following it stay together.

A world of alternatives

In addition to the C compiler included in the GNU Compiler Collection, there are other compilers available. One very popular alternative to gcc is clang, which is part of the LLVM project. Like gcc, it is open source and is installed on the virtual machine intended for solving the exercises. clang accepts largely the same compiler flags as gcc, so you can use it simply by replacing gcc with clang in the commands. In fact, trying clang in addition to gcc is recommended, because in some situations clang may present errors and warnings in a clearer form. Programs that compile with more than one compiler are also a good sign of code quality: a well-written program should compile with different compilers and be more likely to follow the C standard.
Intel and Microsoft, among others, also provide their own C compilers. Intel's compiler in particular has been known for producing highly optimized binaries.

More flags

As was already mentioned in the first material, a
compiler
has about as many
option flags
as there are cats on the internet. In this section we will briefly go through a few that are used on this course.
A particularly important one for this course is -Werror, which can be used by itself or together with an = sign. By itself, it turns all
warnings
into
compiler errors
, meaning that the code will not be compiled into an executable file until the warnings have been fixed. This is also a recommended flag to use. The flag can also be configured so that only a certain warning or set of warnings is treated as an error. A particularly important use on this course is -Werror=missing-prototypes, which is used by the evaluator. This flag causes a program that is missing function
prototypes
to fail compilation. It is therefore a good idea to use it on your own computer as well, so that you can reliably eliminate these errors before submitting your code to the evaluator.
Another warning-related flag is -Wextra, which enables even more warnings than -Wall. The naming is slightly confusing, since "all" does not actually include absolutely everything...
Linux users should also know one more basic flag at this point: -lm. When using functions from the mathematics library (math.h) on Linux, the -lm flag is usually needed. It tells the build process to use the mathematics library. Without this flag, the source code may compile, but the executable file cannot be built because the code that implements the mathematical functions is not available when the executable is created. In this case it is also important that the flag appears in the command after the files being compiled:
/path/to/somewhere$ gcc -Wall -Werror=missing-prototypes -o calculation_program calculation_program.c -lm

Mysterious compiler messages

Being able to read messages from the
compiler
is central to fixing faulty programs. If you have interpreted Python error messages before, there is nothing particularly new here. Largely the same information can be found in these messages as well. Below is code containing a few errors.
These exercises have no effect on course grading.
tkj-rikki-c
#include <stdio.h>
#include <math.h>

const float PI = 3.1416;

float laske_sektorin_ala(float sade, float kulma) {
    return  PI * pow(sade, 2) * kulma / 360;
}

int main() {
    printf("%f\n", laske_sektorin_ala(5));
    return 0;
}

If this code is compiled with the command shown above, the result is:
broken.c:6:7: error: no previous prototype for ‘calculate_sector_area’ [-Werror=missing-prototypes]
 float calculate_sector_area(float radius, float angle) {
       ^
broken.c: In function ‘main’:
broken.c:11:20: error: too few arguments to function ‘calculate_sector_area’
     printf("%f\n", calculate_sector_area(5));
                    ^
broken.c:6:7: note: declared here
 float calculate_sector_area(float radius, float angle) {
       ^
cc1: some warnings being treated as errors
When compiling C, the compiler normally reports several errors in the code at once, whereas with Python the execution of the program stops at the first error and the rest are revealed only after it has been fixed. This behaviour can be changed with the -Wfatal-errors flag. This is mainly useful when compiling the code is a long process. Let us go through this example output one message at a time:
broken.c:4:7: error: no previous prototype for ‘calculate_kinetic_energy’ [-Werror=missing-prototypes]
 float calculate_kinetic_energy(float speed, float mass) {
       ^
This error is caused by the previously mentioned -Werror=missing-prototypes
flag
, which the
compiler
actually also tells us at the end of the first line inside square brackets. Otherwise, the first line consists of the location of the message in the form file name:line number:column number (the column number indicates the position of the character where the error was detected). After the location comes first the type of the message (error), followed by a textual explanation. The second line shows the faulty line, and below it a helpful arrow points to the location where the compiler detected the error. The next message contains one additional line:
broken.c: In function ‘main’:
broken.c:9:20: error: too few arguments to function ‘calculate_kinetic_energy’
     printf("%f\n", calculate_kinetic_energy(5));
                    ^
The first line is new – it tells us in which function the error occurred. The previous message did not originate inside any function, but in the definition section. Otherwise this message is similar to the previous one. This time the error is caused by accidentally giving the function only one argument when it requires two. In fact, the following three lines are also related to this error.
broken.c:4:7: note: declared here
 float calculate_kinetic_energy(float speed, float mass) {
       ^
This message is of type note. It tells us where in the code the function related to the previous error was defined. In other words, it is only additional information. The final line is also an additional note:
cc1: some warnings being treated as errors
This is simply a reminder that at least some warnings were treated as errors.

Final summary

In this material we have covered two basic concepts of C programming, variables and functions, and shown how they differ from Python. A characteristic feature of C is that the types of variables have much more significance. In general, C is somewhat more rigid to write because there are more details that need to be taken into account. On the other hand, this allows us to get closer to the hardware level of the computer. From the point of view of problem solving, however, programming is still largely similar to Python – we still write functions and work with variables. Only the details have changed.
?