Computer Basics in C

Das klare Verständnis über die Grundidee und den Ablauf unseres Classic-Formates ist notwendige Voraussetzung zur Durchführung der Kurse. Bitte hole das jetzt nach, falls bei dir noch Fragen zur Grundidee aufkommen.

Computer Basics in C

Anforderungen an Inspirer

  • Ihr habt alle Kursinhalte wirklich gut verstanden und könnt sie auch flüssig reproduzieren. Wahrscheinlich auch einiges darüber hinaus - Fragen zur Architektur/Funktionsweise von Computern sollten kommen, und ihr solltet auch von weiterführenden Materialien wissen.
  • Ihr wisst, wie eine Unix-Shell funktioniert, und kannst eine verständliche und hilfreiche Einführung dazu halten (+ weißt, wie man manpages öffnet/nutzt). -> Ihr habt selbst verstanden, wie die Sprache C in (fast alle bisherigen) Betriebssysteme eingebettet ist.
  • Ihr könnt mit smarten Teilnehmenden jeden Alters so kommunizieren, dass sie sich ernst genommen fühlen.
  • Ihr könnt auch erfahreneren Teilnehmenden Aufgaben geben, die sie nicht unterfordern - das könnte bedeuten, dass ihr sie schon früher mit den Inhalten im Endprojekt eigenständig herumspielen lässt.
  • Ihr seid in der Lage, den Kurs so abzuändern, dass er allen Teilnehmenden maximal viel Spaß macht, und dabei sowohl sein eigenes Interesse zum Ausdruck bringt, als auch das Interesse der Teilnehmenden fördert.
Dieser Kurs ist für alle, denen es schon immer unter den Fingern brennt, endlich ein paar einfache, aber wichtige Dinge auf Computerebene zu verstehen. Dinge, auf die man aber sonst selten stößt. (Oder erst dann, wenn man die richtigen Fragen kennt, und weiß, was man googlen muss.)
Das bedeutet, dass er eigentlich nur für relativ fortgeschrittene Teilnehmende mit ernsthafter vorheriger Programmiererfahrung (oder sehr großem Interesse) so richtig gut funktionieren wird :)

Im Verlauf des Kurses werden wir uns die Kommandozeile, einfache Ein- und Ausgaben, Chars und Strings anschauen sowie unser eigenes auf der Kommandozeile ausführbares Programm schreiben.
Dabei können die Teilnehmenden (und vielleicht auch ihr) hoffentlich immer besser verstehen, wie Computer auf einer niedrigeren Ebene wirklich funktionieren. Ich hoffe, ihr seid so gespannt wie ich. Viel Spaß!

Zum Vorgehen: Passt euch, hier noch stärker als sonst, an die Teilnehmenden und deren Geschwindigkeit an. Gerade hier ist es besonders wichtig, Zusammenhänge zu verstehen und nicht nur "alles mal gesehen" zu haben.

Und, wie immer: Solltet ihr die Einführung nicht gelesen haben – und wollt diesen Kurs halten – dann holt das doch bitte nach :)

Kennenlernen

Vorstellungsrunde mit Programmiererfahrung/Motivation

Ihr stellt euch vor und erzählt, wie ihr zur Hacker School und zum Coden gekommen seid.
Nun die Teilnehmer:

  • Wie heißt Du? (Achtet darauf, dass die Kinder sich nur mit Vornamen vorstellen – Datenschutz.)
  • Warum bist Du hier?
  • Hast Du schon mal programmiert?
  • Was willst Du lernen?
Vorstellung interaktiver gestalten? (!Klick mich an!)

In unserem Inspirer Handbuch findet ihr
weitere Spielideen.
Wenn Du die Vorstellung etwas interaktiver gestalten möchtest, empfehlen wir das Spiel “Alle, die”, was im folgenden erklärt wird:

Spielidee und Ziel

  • Wir kommen locker und leicht mit den Teilnehmer*innen ins Gespräch und erfahren, was sie gerne mögen (positiver Beziehungsaufbau).
  • Wir können eine erste Diagnose stellen, was die Schülerinnen und Schüler können, was sie gerne mögen.

Ablauf

  1. Stellt Euch vor die Klasse.
  2. Ein*e Inspirer sagt: “Alle, die”-Frage z.B: “Alle, die Sport in der Freizeit machen?”.
  3. Alle, die sich angesprochen fühlen, stehen von ihrem Platz auf.
  4. Die*der Inspirer stellt Rückfragen und kommt ins Gespräch mit den Teilnehmer*innen, z.B. “Cool, du in der ersten Reihe: Wie heißt du? [Antwort] Welchen Sport machst du denn gerne? [Gespräch entsteht] Super und du in der letzten Reihe…”.
  5. Die*der Inspirer sagt: "Alle wieder setzen".
  6. Die*der Inspirer stellt die nächste Frage: z.B. “Alle, die gerne Spiele auf dem Handy oder Computer spielen!”
  7. Siehe 3.-5.
  8. Spielt das Ganze etwa 5 min (nach Gefühl)
Sammlung möglicher Fragen (!Klick mich an!)

Diese Sammlung kann gern erweitert oder angepasst werden.

IT/Programmieren

  • Wer hat schon mal programmiert?
  • Wer kann sich vorstellen etwas mit Programmieren und IT zu machen?

Hobbies

  • Wer von Euch macht Sport in der Freizeit?
  • Wer spielt ein Instrument?
  • Wer spielt Computerspiele?
  • Wer spielt Handyspiele?

Lustiges

  • Wer hat eine Schuhgröße größer als 35?

Letzte Frage

  • Wer ist schon in repl.it angemeldet?

printf()

Wir beginnen – wer hätte es gedacht – mit dem "Hello, World"-Programm.

Dabei "drucken" wir mithilfe von printf Hello, World! auf der Kommandozeile aus. Das geht so:

1
2
3
4
5
6

#include <stdio.h>

int main(void)
{ printf("Hello, World!");
}

printf() kann noch deutlich mehr, und auf Einiges davon werden wir im Verlauf des Kurses noch stoßen.

Variablen und Typen

So. Nachdem wir jetzt grundlegend Dinge ausgeben können, ab zum nächsten Teil: Variablen und deren Benutzung.

Zunächst einmal muss man eine Sache wissen: Variablen besitzen in C einen Typ. Sie können zum Beispiel Zahlen (Typen: short, int, unsigned int, float) oder Buchstaben (Typ: char) sein.
Daraus ergibt sich folgender Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

#include <stdio.h>

int main(void)
{ int num; char c; num = 23; c = 'A'; printf("Hallo, ich bin %i Jahre alt und mein Name beginnt mit %c", num, c);
}

(Spätestens) hier ergibt es Sinn, Folgendes zu erklären:

  • printf() und wie es funktioniert (%i steht für integer, %c für char)
  • #includes
  • Und wahrscheinlich auch Deklarierung und Initialisierung

Funktionen

Schreiben wir unsere Programme allerdings weiterhin auf diese Art und Weise, werden sie schnell unübersichtlich. Eine Möglichkeit, das zu vermeiden, sind Funktionen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

#include <stdio.h>

int twice(int num)
{ return (num * 2);
}

int main(void)
{ int num; num = twice(67); printf("Zahl: %i", num); return (0); //Neu
}

Wichtige Punkte:

  • Der Rückgabewert bzw. der Typ der Funktion
  • Die Parameter und deren Typen
  • return, und wie es mit dem Funktionstyp zusammenhängt.
  • Die Reihenfolge, in der man die Funktionen schreiben muss -> eine Funktion, die ich aufrufen will, muss vorher deklariert worden sein.

Hier ist es super wichtig, dass die Teilnehmenden wirklich verstehen, was passiert. Das rächt sich sonst später. Daher nochmal eine Erinnerung:
Lasst die Teilnehmenden Dinge direkt ausprobieren. Wie wäre es mit einer anderen Funktion – vielleicht int square(int num), int triple(int num) oder add_three(int a, int b, int c)?

Und weiter geht´s.

if/else

Eine weiter Grundlage, die bisher gefehlt hat, sind Verzweigungen. Wir treffen auch im echten Leben Entscheidungen in Abhängigkeit von bestimmten Bedingungen – wir nehmen eine Regenjacke mit, wenn es regnen soll, oder essen nur dann Eis, wenn es wärmer als -5°C ist. Auch beim Programmieren ist das äußerst nützlich.
Wie stellen wir das an?
In C geht das so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

#include <stdio.h>

int main(void)
{ if (1 < 5) { printf("Ja, natürlich ist 1 kleiner als 5. Was denkst du denn?"); } else { printf("Oh, meine Logik scheint ein bisschen kaputt zu sein."); } return (0);
}

Oder doch lieber in einer Funktion?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22

#include <stdio.h>

void ist_kleiner(int zahl1, int zahl2)
{ if (zahl1 < zahl2) { printf("Ja, natürlich ist %i kleiner als %i. Was denkst du denn?", zahl1, zahl2); } else { printf("Oh, %i ist gar nicht kleiner als %i. Was stimmt denn in deinem Kopf nicht?", zahl1, zahl2); } return (0);
}

int main(void)
{ ist_kleiner(1, 5); return (0);
}

chars und ASCII

Für alle Teilnehmenden, die vorher schon programmieren konnten, war das bisher nur eine Wiederholung der Basics.
Hier kommt etwas, das C gegenüber anderen Programmiersprachen besonders macht. Schaut euch diesen Code an.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

#include <stdio.h>

int main(void)
{ char c = 'a'; if('a' == 97) { printf("Oh, was ist denn hier passiert?"); } else { printf("Okay, doch alles gut. Huh, Glück gehabt.") } return (0);
}

So richtig gut scheint nicht alles zu sein. Vielleicht können ja die Teilnehmenden – gemeinsam mit euch – dem Problem auf den Grund gehen?

Eure Lösung führt euch dann hoffentlich über ein bisschen Nachdenken, eine Internet-Suche und die ASCII-Tabelle zurück zu eurem C-Code, in dem die Teilnehmenden ihre Erkenntnisse verarbeiten können.
Um dann, gemeinsam mit euch, zur Erkenntnis zu kommen:

In 'Wirklichkeit' gibt es überhaupt keine chars in C.
Eigentlich gibt es nur deren ASCII-Zahlenwerte, die dann im richtigen Kontext als Buchstaben interpretiert werden.

Das hier wäre ein guter Moment, eine Pause zu machen :)

Arrays

"Wo kommen wir gerade her?/Was haben wir bis hierher gelernt?"

Davor kommt am besten erst einmal ein: "Hallo zurück!" Fragt doch die Teilnehmenden, wie es ihnen geht, was die beste Sache war, die ihnen diese Woche passiert ist, ...

Es lohnt sich, ihnen so erst einmal Zeit zum Ankommen zu lassen (und wird wahrscheinlich auch euch gut tun ;))
Danach könnt nämlich auch gut den bisherigen Inhalt rekapitulieren.

Wir können:

  • Auf die Kommandozeile schreiben (write()/printf())
  • Dinge speichern (Variablen)
  • Aufgaben unterteilen (Funktionen)
  • Code abhängig von Conditions ausführen (if/else)
"Was könnte noch kommen?"
als Frage in die Runde. Hier kann man sehr gut Vorschläge sammeln – vielleicht schafft ihr es ja, später noch auf sie einzugehen.

Was wäre, wenn man sehr viel speichern möchte? -> Auflösung: Man könnte nicht nur für Einzelelemente Namen vergeben, sondern für ganze Bereiche auf einmal. Das kann man mit einem Array machen.
Und zwar so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

#include <stdio.h>

int main(void)
{ int numlist []= {0, 1, 2, 3, 4, 5}; printf("%i", numlist[1]); numlist[0] = 723; printf("%i", numlist[0]); return (0);
}

Loops

Aber was, wenn wir jetzt alle Werte aus dem Array auf einmal ausgeben wollen?
Oder genereller: Wie wiederholen wir Anweisungen beliebig oft?
Dafür gibt es loops (oder Schleifen). Den while-loop benutzt man so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

#include <stdio.h>

int main(void)
{ int numlist[] = {0, 1, 2, 3, 4, 5}; int count; listlength = 6; count = 0; while (count < listlength) { printf("Index %i: Wert %i\n", count, numlist[count]); count++; } return (0);
}

Wichtige Punkte:

  • Die Variablen sollten vor while initialisiert sein.
  • Was macht count++ eigentlich?

Und schon können wir über beliebige Arrays iterieren.

Doch entgegen dem Beispiel im Code können wir Schleifen natürlich nicht nur mit Arrays verwenden. Mit Schleifen kann man jede Anweisung eine beliebig oft ausführen. Falls ihr das Gefühl habt, das ist den Teilnehmenden noch nicht so ganz klar, macht hier gerne noch ein Beispiel dazu (oder fangt gleich mit einem einfacheren Beispiel an:))

Strings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

#include <stdio.h>

int main(void)
{ char char_array[] = {'H', 'a', 'l', 'l', 'o'}; char string[] = "Hallo"; int count; int length; length = 10; count = 0; while (count < length) { printf("Index %i: ASCII-Wert %i, Buchstabe %c\n", count, char_array[count], char_array[count]); count++; } count = 0; while (count < length) { printf("Index %i: ASCII-Wert %i, Buchstabe %c\n", count, string[count], string[count]); count++; } return (0);
}

Was fällt auf?

Hinter dem string steht immer eine 0 (die wir nicht sehen können), und das unabhängig davon, was in string steht. Dieser ASCII-Wert (0) steht für den NULL-Terminator ('\0'), mit dem jeder (C-)String beendet wird.

Das ermöglicht uns zum Beispiel, Strings sehr einfach ausgeben zu lassen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

#include <stdio.h>

int main(void)
{ char char_array[] = {'H', 'a', 'l', 'l', 'u', '\0'}; char string[] = "Hallo"; int count; count = 0; while (char_array[count] != '\0') { printf("Index %i: ASCII-Wert %i, Buchstabe %c\n", count, char_array[count], char_array[count]); count++; } count = 0; while (string[count] != '\0') { printf("Index %i: ASCII-Wert %i, Buchstabe %c\n", count, string[count], string[count]); count++; } return (0);
}

Also: Ein String ist ein Array von chars, der mit '\0' terminiert ist.

Und damit bleibt es dabei: In C gibt es nur Zahlen, und Arrays von Zahlen.

Pointer

Eine weitere Sache, die C besonders macht:
Alle Speicherplätze in unserem Computer haben Speicheradressen, auf die man mithilfe von Pointern (oder Zeigern) zeigen kann. Eine solche Speicheradresse sieht auf einem 32-bit-System so aus:

0x0e469a7d (Eine Ziffer im Hexadezimalsystem ≙ 4 bit)

Mit diesen Speicheradressen, vor allem aber ihren Pointern, können wir in C direkt arbeiten. Zum Beispiel so:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

#include <stdio.h>

void write_seventeen(int *num_pointer)
{ *num_pointer = 17;
}

int main(void)
{ int number; number = 10; printf("number is right now: %i", number); printf("and its location is: %p", &number); write_seventeen(&number); printf("In number is now %i", number); return (0);
}

Einige Anmerkungen:

  • Der * in Zeile 3 zeigt den Typ der Variable num_pointer an. (Der Typ ist Pointer auf int)
  • Der * in Zeile 5 macht etwas anderes: Er dereferenziert den Pointer num_pointer
  • Das & in Zeile 13 gibt write_seventeen() eine Referenz auf number (also einen Pointer auf number) mit.

Anmerkung zur Anmerkung: Gerade gegen Anfang können Pointer sehr verwirren – malt euch/den Teilnehmenden doch da eine Grafik :) (Auf Repl kann man sich mit <dateiname>.draw ein Whiteboard erstellen.)

All das wirkt jetzt wahrscheinlich ziemlich umständlich dafür, dass wir nur den Wert einer Variable ändern wollen. Es ist allerdings notwendig, da das der einzige Weg ist, wie man in C Variablen innerhalb einer anderen Funktion mutieren kann. Funktionen bekommen ansonsten nämlich nicht die Aufrufargumente selbst, sondern lokale Kopien von ihnen mitgegeben. Das ist als Schutz gedacht, durchaus sinnvoll ist. Da wir diesen hier aber absichtlich umgehen wollen, müssen wir das auf diese Art und Weise explizit(er) machen.

Da Pointer aber auch 'nur' Zahlen sind, können wir ganz normal mit ihnen rechnen. Folgt man diesen veränderten Pointern allerdings, kann alles Mögliche passieren.

Wer weiß schon, was am Speicherplatz (num_pointer + 10) steht?
C kann es dir mit *(num_pointer + 10) sagen ;)

Passt also auf, wenn ihr mit Pointern arbeitet.

Kurz zusammengefasst: Ein Pointer ist ein Zeiger auf einen Speicherplatz, dem man mit *ptr folgen kann, um den Inhalt des Speicherplatzes zu erhalten.

int main(int argc, char *argv[])

So. So langsam kommen wir unserem Ziel näher. Wir sind nämlich schon sehr bald in der Lage, unser erstes eigenes Kommandozeilenprogramm zu schreiben, das Argumente annehmen und verarbeiten kann.
Doch testet zunächst einmal, was unsere bisherigen Programme tun, wenn man ihnen Argumente mitgibt. Dafür hören wir damit auf, auf den Run-Button zu drücken.
Wir kompilieren unser Programm stattdessen von Hand, um es danach dann auch von Hand ausführen zu können.

Das macht ihr, indem ihr rechts von Console auf Shell wechselt. Dort gebt ihr dann gcc main.c ein. Eine Datei namens a.out sollte erscheinen. Herzlichen Glückwunsch! Das ist euer erstes selbst kompiliertes Programm.

1
2
3
$ gcc main.c
$ ls
a.out main main.c

Ein Programm führt ihr aus, indem ihr ./<programm-name> schreibt:

1
$ ./a.out hi ich bin

Was passiert?

Richtig. Gar nichts. Das liegt daran, dass wir main() ja auch noch gar nicht sagen, dass wir mit seinen Argumenten etwas anfangen möchten. Das können wir erst, wenn wir die main-Funktion so abändern:

1
int main(int argc, char *argv[]);

Anstatt dass wir void, also nichts, mitgeben, ist hier argc nun die Anzahl von Argumenten, die wir mitgeben, und argv ein Array von Pointern, die auf Strings zeigen.

Lasst uns die Argumente benutzen, um sie auszugeben.

Was fällt auf?
Anscheinend ist der Programmname selbst das erste Element in argv :)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

#include <stdio.h>

int main(int argc, char *argv[])
{ int count; count = 0; while (count < argc) { printf("%s\n", argv[count]); count++; } return (0);
}

Projekte

Und damit sind wir auch schon (fast) am Ende unserer ziemlich langen Lernphase angekommen. Nun geht die Initiative endgültig zu den Teilnehmenden. Gibt es etwas, das sie gerne selbst coden/ausprobieren würden?
Macht noch ein letztes Mal deutlich, dass ihr hier seid, um ihnen zu helfen.

Falls es wenig bis keine Ideen gibt, lohnt es sich vielleicht auch, sie einfach über die neu gelernten Dinge sprechen zu lassen.
Ansonsten hier ein paar Ideen:

  • Ein Taschenrechner, der folgende/oder eine ähnliche Funktionalität hat:

    1
    2
    3
    4
    $ ./a.out + 2 3 3 5 2 8 6
    29
    $ ./a.out * 2 3 1 4
    24
    
  • Ein Programm, das ausgibt, welcher von zwei eingegebenen Strings länger ist

    1
    2
    3
    4
    5
    $ ./a.out hallo hi
    Der Erste
    $ ./a.out ich bins
    Der Zweite
    $ ./a.out ich du wir
    

    Was, findet ihr, sollte das Programm im letzten Fall zurückgeben/machen?

Optional(!): Assembly Code

Bisher haben wir uns sehr erfolgreich auf der Höhe der Shell vorangearbeitet.
Falls es immer noch jemanden gibt, der noch Lust auf mehr hat:

Was passiert eigentlich mit unserem Code beim Kompilieren wirklich?
Ernsthaft, falls ihr jemals eine*n Teilnehmende*n haben solltet, der hier ankommt, meldet euch.

Die erste Hälfte der Antwort liefert uns der Befehl

1
$ gcc -S main.c

Dieser produziert eine Datei mit der Endung .s, in diesem Fall die Datei main.s. Dort findet ihr die Befehle, die der Computer "in Wirklichkeit" ausführt. Von hier aus bis zu echtem Maschinencode ist es dann nur noch ein sehr kleiner Schritt, das ist dann allerdings nicht mehr/nur noch sehr schwer von Menschen lesbar. Diese Aufgabe übernimmt in unserem Fall der C-Compiler, ansonsten könnte sie aber auch ein sogenannter Assembler übernehmen.

Auch hiermit kann man sehr gut herumspielen, und mit einer kleinen Internetrecherche findet man auch heraus, was die meisten Assemblerbefehle machen.

Aber das ist vielleicht einmal Teil eines anderen Kurses – dieser endet nämlich hier.
Macht gerne noch eine große Abschlussrunde mit den Teilnehmenden, in der sie sich gegenseitig ihre Projekte vorstellen können – oder auch einfach nur euch ausfragen und sich Feedback abholen.

Großen Respekt euch, das bis hierher gelesen zu haben durchgehalten zu haben. Ich hoffe, es hat Spaß gemacht, und ihr habt auch ein bisschen mehr über C gelernt. Und vergesst nicht, auch wir freuen uns über Feedback :)