| marp | true |
|---|---|
| title | Programming 1 |
| author | Benjamin Wilking |
| theme | uncover |
| style | section { font-size: 30px } |
| class | invert |
| paginate | false |
The goal of this course is to enable all participants to learn proper software engineering. Being a good software engineer takes time, will to improve yourself and commitment.
<style scoped> section{ font-size: 20px; } </style>
- Literature
- Exercises@Home & Exam
- Some Linux Basics
- The Language C
- The basics Example
- Git
- Common Environment
- Basics in C #1
- What is Compiling?
- CMake
- Debugging
- Visual Studio Code
- User Input in C
- Basics in C #2
- Functions
- Libraries
- Const Correctness
- Continuous Integration, Delivery & Deployment
- Homework is voluntary but strongly recommended.
- You are welcome to ask questions, I'm glad to help.
- During the last 3 weeks of the course.
- In teams of up to 2 people.
- Delivery in Git.
Simply put, the shell is a program that takes commands from the keyboard and gives them to the operating system to perform. [...] (linuxcommand.org)
The most common shell is the bash (Bourne Again SHell). Code snippets in this course are given for bash.
The zsh (Z shell) is getting more and more popular and is already the standard on some systems (e.g. Apple).
If you open a terminal with a zsh, you can always type in bash and you will get a bash shell.
<style scoped> h1 {font-size: 60px} {font-size: 18px} </style>
- go to home
cd ~or justcd - go to last folder
cd - - show current directory
pwd - list content of current folder
ls -la(man ls) - create a new file
touch my_new_file.ext - create a new directory
mkdir my_new_dir - go to directory
- absolute path
cd /home/<username>/my_new_dir - relative path
cd ./my_new_dir - one level up
cd ..
- absolute path
- open file in editor
nano my_new_file.ext(or any other editor) - rename (move) a file
mv my_new_file.ext my_new_file.new_ext - remove file
rm my_new_file.new_ext - remove directory
rm -rf my_new_dir(:warning: very dangerous, always double check) - put some text to a file
echo "some strange text" >> my_textfile.txt - find text in file
grep -rnw '.' -e 'strange' - ☝️ "double-tab" completes your input and gives you your possibilities
- ⬆️ goes through the command history
- Go to your home folder.
- Create a new file in a new folder.
- "echo" some text to the file
- open the file with an editor and add more lines
- find a certain string in the file with grep
- create a second empty file
- delete the second file
- delete the created folder
(in between, always check the current status with ls)
C [...] is a general-purpose computer programming language. It was created in the 1970s by Dennis Ritchie, and remains very widely used and influential. [...]. C is commonly used on computer architectures that range from the largest supercomputers to the smallest microcontrollers and embedded systems. (wikipedia)
If programming languages were vehicles
Create a new file basics.c with the following content
int main()
{
return 0;
}compile it
gcc basics.c # using the gnu compiler
clang basics.c # using the clang compilerthe result is a binary called a.out.
You can run the program with ./a.out
If you want to give the binary a better name use the "-o" option
gcc basics.c -o basics You can check all possible options with
man gcc # or 'man clang'Finally, you can run the binary with
./basics☝️ you can check the return value with: ./basics; echo $?
Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. (Git)
In software engineering, version control (also known as revision control, source control, or source code management) is a class of systems responsible for managing changes to computer programs, documents, large web sites, or other collections of information. (wikipedia)
TensorFlow is an end-to-end open source platform for machine learning. (TensorFlow@GitHub)
[Example Blame](https://github.com/tensorflow/tensorflow/blame/master/tensorflow/c/tf_tensor.cc)
mkdir test_repo && cd test_repo
git init # initialize the repository
git status # check status of repository
echo "my first line" > first_file.txt # write something to a new file
# -> check status
git add first_file.txt # add the file to version control
# -> check status
git commit -m"initial commit (short msg)" -m"description (long msg)"
# commit the change to the repo
# -> check status
gitk # opens the git native commit graph
git gui # opens the git native user interface
git log # check your changes on the command line
git log --graph # show the commit graph on command line- create a branch with
git branch my_new_feature - check the available branches with
git branch - switch to you new branch with
git checkout my_new_feature - add a new file and edit the one you already have
- commit your changes
- switch back to your main/master branch
- merge your changes to your main/master with
git merge my_new_feature - delete your feature branch with
git branch -D my_new_feature - check your commit graph
☝️ always remember to check your work with git status
<style scoped> h3 {font-size: 50px} {font-size: 25px} </style>
-
On GitHub create a new repository from the learn2code template
-
Add an SSH key for your machine
Go to the new repository you created in the last step and get the URL: CODE ➡️ local ➡️ SSH
git clone <InsertYourRepoURL> # get the repo
cd <YourRepoName> # go into the repo folder
echo "some text" > <your file name> #write text to file
# add and commit your changes
git push # bring local changes to the remote repo- do not add binary (large) files
- always write a descriptive message (short + long)
- use .gitignore to keep your workspace clear
- get familiar with the command line
- learn the best practices
- Maybe you don't have a linux PC
- We all have the same system (compiler etc.)
- If it works on your machine, it works on my machine
- Necessary tools and configs are already installed
Gitpod is an open-source Kubernetes application for ready-to-code cloud development environments that spins up fresh, automated dev environments for each task, in the cloud, in seconds. (gitpod@github)
You can use this repo and the corresponding gitpod
A codespace is a development environment that's hosted in the cloud. You can customize your project for GitHub Codespaces [...], which creates a repeatable codespace configuration [...]. (github)
You can use this repo and the corresponding codespace
The C standard library or libc is the standard library for the C programming language, as specified in the ISO C standard. [...].
The C standard library provides macros, type definitions and functions [...]. (wikipedia)
#include <stdio.h> //specify where to look for the functions you need
int main()
{
/* print something to the command line
make sure to end with a new line "\n"
you can find more "control-characters"
on e.g. https://en.wikipedia.org/wiki/Control_character
BTW: this is a multi-line comment */
printf("Hello World!\n");
return 0;
}#include <stdio.h> //specify where to look for the functions you need
//define a macro, from now on we can use "new_line" instead of "\n"
#define new_line "\n"
int main()
{
// append the macro to the string
printf("Hello World!"new_line);
return 0;
}As a programmer you want to work with variables to remember a certain value. In C, there are three kinds of basic data types for variables
- int - integer (whole number) values
- float - floating point values
- char - single character values (such as “m” or “Z”)
Defining and using a variable in the code is quite easy:
int main()
{
int x; // defines an integer variable named x
float y = 1.0; // defines a variable and assigns a value
x = 5; // initialize x before first usage
x = x+x; // re-assign a value to x
return 0;
}Using printf, the output depends on the datatype!
- int (integer values) uses %d
- float (floating point values) uses %f
- char (single character values) uses %c
- character strings (arrays of characters, discussed later) use %s
printf("my int %d; my float %.2f; my char %c\n", x, y, c)+− addition−− subtraction/− division∗− multiplication%− modulo
Operator precedence is comparable to mathematics.
There are many more, you should go through them at least once!
Some operators make life easier!
x = x + 1; <-> ++x;
x = x + y; <-> x+=y;int a = 1;
int b = a++; // stores 1+a (which is 2) to a
// returns the value of a (which is 1)
// After this line, b == 1 and a == 2
a = 1;
int c = ++a; // stores 1+a (which is 2) to a
// returns 1+a (which is 2)
// after this line, c == 2 and a == 2(example from here)
int main()
{
// declare a structure and name it "my_struct"
struct my_struct
{
int a,b,c;
float d,e,f;
}ms1; // instantly instantiate a structure of this type with name ms1
// instantiate a structure without initialization -> be careful
struct my_struct ms2;
// instantiate a structure with initialization
struct my_struct ms3 = {.a=5, .b=6, .c=7, .d=0.1, .e=0.2, .f=0.3};
// access elements of a structure
int k = ms3.a + ms3.b + ms3.c;
return 0;
}// globally defined structure (outside of main)
// works equivalent to define a structure inside of main
struct outer_struct
{
int a,b,c;
float d,e,f;
};
// declare a new type of structure
// typedef can be used to define any kind of new type
// https://en.cppreference.com/w/c/language/typedef
typedef struct
{
int a,b,c;
float d,e,f;
} outer_td_struct;
int main()
{
//locally instantiated structure "or" with direct initialization
struct outer_struct or = {.a=5, .b=6, .c=7, .d=0.1, .e=0.2, .f=0.3};
//using the new type of structure to instantiate structure "otd"
outer_td_struct otd = {.a=5, .b=6, .c=7, .d=0.1, .e=0.2, .f=0.3};
return 0;
}int main()
{
int a[5]; // declare an array without initialization -> be careful
int b[5] = {0}; // initialize all values with zeros
int c[5] = {22, 33, 44, 55, 66}; // initialize all values explicitly
int d[] = {22, 33, 44, 55, 66}; // you don't need to specify the size
int e[5];
e[0] = 10; // !!! array indices always start at 0 ....
e[1] = 3;
e[2] = 13;
e[3] = 22;
e[4] = 77; // ... and end with n-1
return 0;
}int i, j;
int *p; // define pointer to integer. p holds a random unknown address now
// trying to use *p most probably creates a seg fault
int *pi = NULL; // now pointer is initialized. Still a segfault, but we can check it
printf("uninitialized: p points to: %p\n", p); // %p is "pointer address"
p = &i; // assign the address of i to p
printf("initialized p, uninitialized i: value *p=%d, p points to %p\n", *p, p);
*p = 5; // assign value to the memory of the pointer
printf("initialized p, initialized i: value *p=%d, p points to %p\n", *p, p);
j = i; // copy the value from i to j
printf("i=%d, j=%d, value *p=%d, p points to %p\n", i, j, *p, p);
struct my_struct
{
int a;
float b;
} my1 = {.a = 2, .b = 3.3f};
struct my_struct *p_my2 = &my1;
// accessing elements of pointer-to-struct is done by "->" instead of "."
printf("Struct value a=%i from instance; struct value b=%.1f from pointer!\n", my1.a, p_my2->b);// sizeof returns the size of a datatype in bytes
// lets test the size of some basic datatypes
printf("size of char: %lu\n", sizeof(char));
printf("size of int: %lu\n", sizeof(int));
printf("size of float: %lu\n", sizeof(float));
printf("size of double: %lu\n", sizeof(double));
printf("size of long: %lu\n", sizeof(long));
printf("size of char*: %lu\n", sizeof(char*));
printf("size of int*: %lu\n", sizeof(int*));
printf("size of float*: %lu\n", sizeof(float*));
printf("size of double*: %lu\n", sizeof(double*));
printf("size of long*: %lu\n", sizeof(long*));gcc -E basics_in_c_macro.c
# or
clang -E basics_in_c_macro.cCan you figure out what the preprocessor does?
clang -c basics_in_c_macro.c
nano basics_in_c_macro.o # naive approach to show content of object file
objdump -D basics_in_c_macro.o # use linux tools to inspect the fileFigure out the options in the following command. What are they doing?
clang -Wall -Wextra -Werror -pedantic -O0 basics_in_c_macro.cAdditional compiler flags can help you in finding bugs very early. A typical example is the implicit conversion
int b = 2;
float a = 5.0F;
a = 5.0 / b;
printf("float a = %.3f\n", a); // seems to work correctly -> a = 2.500
a = 5 / b;
printf("float a = %.3f\n", a); // definitely wrong output -> a = 2.000You could find this problem with the gcc flag -Wconversion
We did not call the linker! Well, let's check if we linked something
On Linux
ldd a.outOn Mac
otool -L a.out#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}clang basics_in_c.c && ./a.outprint("Hello World!")python3 basics_in_c_python.pyWhat are the advantages of the interpreter language? What are the downsides?
CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files [...]. (CMake)
Book recommendation: Professional CMake
CMake config is always a file named CMakeLists.txt
# define the minimum cmake version
cmake_minimum_required(VERSION 3.20)
# define the project name
project(cmake_example VERSION 1.0 LANGUAGES C)
# define the c standard version
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED True)
# add some compile options
add_compile_options(-Wall -Wextra -pedantic -O0)
# add an executable and assign files to it
add_executable(cmake_example_exec cmake.c)cd <folder-with-CMakeLists.txt>
cmake . # run cmake
make # build your project
./cmake_example # run your binaryResult: your workspace gets polluted 😒
Define sources and build folder
cmake -S . -B buildStill, there are some things undefined. Add:
-G "Unix Makefiles"
-D CMAKE_C_COMPILER=gcc
-D CMAKE_CXX_COMPILER=g++ (you could also use e.g. the llvm toolchain with clang-16 and clang-cpp-16)
Figure out what these options are doing with:
cmake --helpStill, calling CMake seems to be tedious 😓 Let's create a small script for that. Create a file called run_cmake.sh
#!/bin/bash
cmake -S . -B build #add all your options- You probably recognized, that you have to go to the build folder before calling
make😉 - Can you run your script run_cmake.sh? Probably not! Try
chmod +x run_cmake.sh! - What does this?
In computer programming and software development, debugging is the process of finding and resolving bugs (defects or problems that prevent correct operation) within computer programs, software, or systems. (wikipedia)
-
you have to tell the compiler to create debug symbols
- extend CMake build with:
cmake -S . -B build -DCMAKE_BUILD_TYPE=DEBUG -
either use gdb or lldb
(:warning: unfortunately not working on codespace / gitpod)
lldb debugging_example_exec # this is the name of your binary defined in CMakeLists.txt
(lldb) breakpoint set --file debugging.c --line 12 # this is the name of your source file
(lldb) process launch
(lldb) help # to get the possible commands
(lldb) v # shows all variables
(lldb) v val1 # shows only variable val1
(lldb) s # step to next line or step into current function
(lldb) c # continue to next breakpoint or end
(lldb) exit # leave lldbgdb debugging_example_exec # this is the name of your binary defined in CMakeLists.txt
(gdb) break debugging.c:12 # this is the name of your source file
(gdb) run
(gdb) help # to get the possible commands
(gdb) info locals # shows all variables
(gdb) p val1 # shows only variable val1
(gdb) s # step to next line or step into current function
(gdb) next # continue to next breakpoint or end
(gdb) q # leave gdbUntil now, we did everything on the command line. It is time to get a bit lazy 😏 (You have to have the CMake Tools Extension installed)
- right click CMakeLists.txt → Configure All Projects
- If you are asked for to select a kit → select one for linux

After configuring the project, you can build, run & debug your project using the extension buttons on the bottom of the window.
There, you can also change:
- build toolkit
- Debug/Release configuration
- build target ...
❗ If you cant see the CMake status bar, add this line to your .vscode/settings.json file
"cmake.options.statusBarVisibility": "compact",There are also several Git Extensions. A very good one is Git Lense. Even if is absolutely mandatory to know the git command line, VS Code gives you some comfort:
- Check the git symbol on the left of VS Code
- Check the additional right click actions for files
- ...
Using a common coding format eases collaboration on code a lot. Common coding format contains rules about: braces, brackets, number of spaces (tab usage), spaces between operators, and many more.
VS Code provides an easy way to format your files automatically.
- install clang-tidy
- install VS Code extension xaver.clang-format
- copy the .clang-format config file to your main folder
- open a file and press Ctrl + ⇧ + P (⌘ + ⇧ + P)
- search for format document → click to execute
- Reading user input is as easy as printing something.
- Similar to printf we use scanf to read in values.
- The type of data is important, e.g.
%dfor integer values
// read in an integer value from the console
scanf("%d", &val1) ;☝️ The & sign is very important. We learned that in the chapter pointers
int b;
printf("Enter a value:");
scanf("%d",&b);
if(b < 0)
{
printf("The value is negative\n");
}
else if(b==0)
{
printf("The value is zero\n");
}
else
{
printf("The value is positive\n");
}Read more about comparison operators
int a = 2;
switch (a)
{
case 1:
printf("a is one\n");
break;
case 2:
printf("a is two\n");
break;
case 3:
printf("a is three\n");
break;
default:
printf("a is something else\n");
break;
}int a = 0, b = 10;
while (a < b)
{
printf("%d\n", a);
++a;
}int a = 0, b = 10;
do
{
printf("%d\n", a);
++a;
}while (a < b);The for loop in C is simply a shorthand way of expressing a while statement (The Basics of C Programming)
int a = 0, b = 10;
while (a < b)
{
printf("%d\n", a);
++a;
}int a, b;
for (a = 0, b = 10; a < b; ++a)
{
printf("%d\n", a);
}So far, we wrote spaghetti-code 🍝 We can change this by defining a function
return_value my_function_name(<datatype> input_1, <datatype> input_2)
{
<do something>
return <a value>;
}Put this IN FRONT of your main and you can use the function in the code!
The overview is not very good having all the functions in front of the main. You can create a prototype only instead:
return_value my_function_name(<datatype> input_1, <datatype> input_2);Now, you can define the function AFTER the main
int main(void) {return 0;}
return_value my_function_name(<datatype> input_1, <datatype> input_2)
{
<do something>
return <a value>;
}Even with the prototype, the overview is not perfect. Putting code in separate files would help!
- Create a header file for the prototype (e.g. include/functions.h). Always use include guards
- Create a source file for the definition (e.g. src/functions.c)
- Include the header in your main file
- Tell the compiler where to find everything (CMakeLists.txt ➡️ target_include_directories)
/*
always put an "include guard" on top.
good practice is to use the uppercase file name + a random string.
In big projects you never know if somebody else named the file and the
include guard the same.
For random Strings use e.g. https://www.random.org/strings/
*/
#ifndef FUNCTIONS_H_18RqkXbbAj
#define FUNCTIONS_H_18RqkXbbAj
// prototypes and definitions go here!
#endif /* FUNCTIONS_H_18RqkXbbAj */random string online or VS Code extension insert-random-text
From now on, we will stick to the following folder structure
- <project-name>
include( ➡️ contains all header files)src( ➡️ contains all source files)test( ➡️ contains test files)doc( ➡️ contains relevant files for documentation)CMakeLists.txtreadme.md- ...
So, we learned how to extract code into extra files 🎇. It could be even better. How about creating an own library - like the C standard library we use all the time?
On Linux and using CMake, creating a library is dead simple ✨
# add a library
add_library(<library_name> <STATIC | SHARED> <source_files>)
# add the includes to the library
target_include_directories(<library_name> PUBLIC include)Now, you can use the new library for your executable
# add an executable and assign files to it
add_executable(<exec_name> src/main.c)
# add the include directories
target_include_directories(<exec_name> PUBLIC include)
# link your libraries
target_link_libraries(<exec_name> PRIVATE <library_name>)To be honest: using (3rd party) libraries can be extremely tedious 😢
- Create a static library which contains a function to add two integers
- Create a shared library which gives you a nice Batman print
- Create a main which consumes (links and uses) both libraries
- Figure out what the Linux tool ldd does
- Check your compiled main with ldd, what do you see (and what not)?
/* declare a const value variable */
const int value_c = 33; // const value variable
value_c = 0; // assign a new value -> ERROR/* declare a normal pointer */
int* ptr = &value_a;
ptr = &value_b; // assign a new memory address
*ptr = 33; // assign a new value
/* declare a const value pointer */
int const * const_s_ptr = &value_a; // const value pointer
const_s_ptr = &value_b; // assign a new memory address
*const_s_ptr = 44; // assign a new value -> ERROR
/* declare a const address pointer */
int * const s_const_ptr = &value_a; // const address pointer
s_const_ptr = &value_b; // assign a new memory address -> ERROR
*s_const_ptr = 55; // assign a new value
/* declare a const value and const address pointer*/
int const * const const_s_const_ptr = &value_a; // const value const address pointer
const_s_const_ptr = &value_b; // assign a new memory address -> ERROR
*const_s_const_ptr = 44; // assign a new value -> ERRORint* my_const_correct_function(int * mutable_ptr, int const * const immutable_ptr)
{
int * inside_ptr;
int const * const inside_csc_ptr = immutable_ptr;
inside_ptr = mutable_ptr; // fine
++*inside_ptr; // fine
++*mutable_ptr; // fine
//inside_ptr = immutable_ptr; // BAD, warning: assignment discards ‘const’ qualifier
//++*immutable_ptr; // ERROR, can not change the pointer value or address
return inside_ptr; // fine
//return inside_csc_ptr; // BAD, return discards ‘const’ qualifier
}int* return_ptr = my_const_correct_function(ptr, s_const_ptr);In software engineering, continuous integration (CI) is the practice of merging all developers' working copies to a shared mainline several times a day. (wikipedia)
Continuous delivery (CD) is a software engineering approach in which teams produce software in short cycles, ensuring that the software can be reliably released at any time and, when releasing the software, without doing so manually. (wikipedia)
Continuous deployment (CD) is a software engineering approach in which software functionalities are delivered frequently and through automated deployments. (wikipedia)
Github Actions allow you to continuously integrate, deliver and even deploy your software.
For now, we want to use Github Actions to build and test our software continuously.
☝️ There is way more we could do, but that's the absolute minimum
You will get a first proposal for your workflow file. E.g. .github/workflows/cmake.yml In this file you need to make sure, that























