C
C-Grundlagen Teil 2 – Typen in C verstehen
C-Grundlagen Teil 2 – Typen in C verstehen
Im ersten Teil ging es darum, warum C nach 50 Jahren immer noch das Fundament von allem ist.
Jetzt geht es richtig los. Und zwar da, wo jede Sprache anfängt: bei den Typen.
In C sind Typen ehrlicher als überall sonst: Ein Typ sagt dem Compiler, wie viele Bytes ein Wert belegt und wie diese Bytes zu interpretieren sind. Mehr Magie gibt es nicht. Genau deshalb versteht man Typen nirgendwo so gründlich wie in C.
Was ist überhaupt ein Typ?
Im Speicher liegen nur Bytes. 01000001 kann die Zahl 65 sein – oder der Buchstabe 'A'. Der Typ entscheidet, wie die Bytes gelesen werden.
Dein Werkzeug zum Reinschauen ist printf mit Format-Spezifizierern:
#include <stdio.h>
int main(void)
{
printf("%d\n", 42); // %d: int → 42
printf("%f\n", 4.2); // %f: double → 4.200000
printf("%c\n", 'A'); // %c: Zeichen → A
printf("%d\n", 'A'); // %d: als Zahl → 65 (!)
return 0;
}Die letzte Zeile ist schon die erste Lektion: Dasselbe Zeichen, anders interpretiert, gibt eine Zahl. Willkommen in C.
Zum Ausprobieren reicht ein Online-Compiler wie OnlineGDB – nichts installieren, einfach loslegen.
Die Basis-Typen
int – Ganzzahlen
int alter = 34;
int temperatur = -5;
int jahr = 2026;Ganze Zahlen, positiv oder negativ. Auf heutigen Plattformen üblicherweise 4 Bytes, also etwa ±2,1 Milliarden.
„Üblicherweise“ ist wörtlich gemeint: C garantiert nur Mindestgrößen, die exakte Größe hängt von der Plattform ab. sizeof verrät sie:
printf("%zu\n", sizeof(int)); // 4 (auf den meisten Systemen)
printf("%zu\n", sizeof(long)); // 8 auf 64-Bit-Linux, 4 auf Windows (!)Wer exakte Größen braucht (Embedded!), nimmt int32_t, uint8_t & Co. aus <stdint.h>.
float und double – Kommazahlen
float preis = 19.99f; // 4 Bytes, das f-Suffix macht es zum float
double pi = 3.14159; // 8 Bytes – der Standard für KommazahlenWichtig: Der Dezimaltrenner ist ein Punkt, kein Komma. Und: Kommazahlen-Literale sind in C standardmäßig double – float nimmt man heute fast nur noch, wenn Speicher knapp ist.
Die Genauigkeits-Falle der Floats kommt im Zusatz am Ende – sie ist in C dieselbe wie überall.
char – ein Zeichen, das eigentlich eine Zahl ist
char note = 'A';
printf("%c\n", note); // A
printf("%d\n", note); // 65
printf("%c\n", note + 1); // B (!)Ein char ist 1 Byte – eine kleine Zahl, die per Konvention als Zeichen interpretiert wird. Mit Zeichen kann man deshalb rechnen. 'A' + 1 ist 'B'. Das ist kein Trick, das ist das ganze Geheimnis.
bool – gibt es, aber nur auf Nachfrage
Historisch hatte C keinen Wahrheitstyp: 0 ist falsch, alles andere ist wahr.
#include <stdbool.h>
bool istAngemeldet = true;
if (istAngemeldet) {
printf("Willkommen!\n");
}Seit C99 gibt es bool über den Header <stdbool.h> (seit C23 sogar ohne). Benutzen! int als Wahrheitswert ist eine Altlast, keine Empfehlung.
Strings – die unbequeme Wahrheit
Jetzt kommt der Punkt, an dem C sich am meisten von PHP, Java und allem anderen unterscheidet:
C hat keinen String-Typ.
Ein „String“ in C ist ein Array aus char, das mit einem Null-Byte ('\0') endet:
char name[] = "Marcel";
// Im Speicher liegt: 'M' 'a' 'r' 'c' 'e' 'l' '\0'
printf("%s\n", name); // Marcel
printf("%zu\n", sizeof(name)); // 7 – sechs Zeichen plus das Null-Byte!Das '\0' ist die einzige Information darüber, wo der String aufhört. Funktionen wie strlen laufen einfach los und zählen, bis sie es finden:
#include <string.h>
printf("%zu\n", strlen(name)); // 6 – ohne das Null-ByteUnd genau hier wohnen die berüchtigten C-Bugs: Fehlt das Null-Byte, liest strlen fröhlich fremden Speicher weiter. Deshalb ist „String-Handling in C“ ein eigenes Thema für einen späteren Teil – für heute reicht: Ein String ist ein char-Array mit Abschluss-Byte. Keine Magie.
Implizite Konvertierungen – Cs stille Falle
C konvertiert Typen automatisch, wenn sie in einem Ausdruck gemischt werden. Meistens geht das gut. Manchmal genau nicht:
int a = 10;
int b = 3;
printf("%d\n", a / b); // 3 – int / int bleibt int, Rest wird abgeschnitten
printf("%f\n", a / (double)b); // 3.333333 – der Cast erzwingt double-Division
printf("%f\n", (double)(a / b)); // 3.000000 (!) – zu spät gecastet, Schaden schon passiertDie dritte Zeile ist der Klassiker: Erst rechnet C 10 / 3 = 3 in int, dann wird die 3 zum double. Der Cast muss vor die Division, nicht davor geklatscht ans Ergebnis.
Zweite Falle: unsigned. Vorzeichenlose Typen können nicht negativ werden – sie wickeln stattdessen um:
unsigned int u = 0;
u = u - 1;
printf("%u\n", u); // 4294967295 (!)Kein Fehler, keine Warnung zur Laufzeit. C tut exakt, was man sagt – auch wenn man etwas anderes meinte.
Typen in Funktionen
In C deklariert man Parameter- und Rückgabetypen seit jeher – ganz ohne Diskussion:
double berechne_preis_mit_steuer(double netto_preis, double steuersatz)
{
return netto_preis * (1 + steuersatz);
}
int verdopple(int zahl)
{
return zahl * 2;
}Aber Achtung, C ist beim Prüfen großzügiger als Java oder Kotlin: verdopple(4.9) kompiliert – der double wird still zu 4 abgeschnitten. Der Compiler warnt bestenfalls.
Deshalb die wichtigste C-Gewohnheit ab Tag 1: Immer mit Warnungen kompilieren.
gcc -Wall -Wextra programm.c -o programm-Wall -Wextra ist das C-Äquivalent zu strict_types in PHP: Es macht die stillen Umwandlungen sichtbar, bevor sie zu Bugs werden.
Aufgaben
Genug Theorie.
Am meisten hängen bleibt, wenn man es selbst tippt. OnlineGDB auf und los.
Aufgabe 1: Typen raten
Was gibt dieses Programm aus? Erst überlegen, dann ausführen.
#include <stdio.h>
int main(void)
{
printf("%d\n", 10 / 3);
printf("%f\n", 10 / 3.0);
printf("%d\n", 'A' + 1);
printf("%c\n", 'A' + 1);
printf("%d\n", 5 == 5.0);
return 0;
}Aufgabe 2: Steckbrief
Lege ein struct Profil mit name (char-Array), alter (int) und anzahl_hobbys (int) an.
Erzeuge eine Instanz und gib einen Satz aus wie: Marcel ist 34 und hat 3 Hobbys.
Tipp: %s, %d und eine Portion printf.
Aufgabe 3: sizeof-Detektiv
Gib mit sizeof die Größe von char, int, long, double und einem char[20] aus.
Vorher aufschreiben, was du erwartest – dann prüfen. (Format-Spezifizierer für sizeof: %zu)
Aufgabe 4: Der Geld-Klassiker
Ein Kunde kauft drei Artikel zu je 0.10 €. Prüfe mit ==, ob die Summe 0.30 ergibt.
Falls nicht: Rechne dasselbe noch einmal in Cent als int und prüfe erneut.
Aufgabe 5: Das Null-Byte erleben
Lege char name[] = "Marcel"; an und gib sizeof(name) und strlen(name) aus.
Setze dann name[3] = '\0'; und gib den String und beide Werte erneut aus. Erkläre, was passiert ist.
Lösungsvorschläge
Erst selbst probieren. Ehrlich. Dann vergleichen.
Lösung zu Aufgabe 1
printf("%d\n", 10 / 3); // 3 – int-Division schneidet ab
printf("%f\n", 10 / 3.0); // 3.333333 – double im Spiel, also double-Division
printf("%d\n", 'A' + 1); // 66 – 'A' ist 65, plus 1
printf("%c\n", 'A' + 1); // B – dieselbe 66, als Zeichen interpretiert
printf("%d\n", 5 == 5.0); // 1 – wahr; der int wird zum double erweitert, C gibt 1 für wahr ausZeile 3 und 4 sind der Kern von C in zwei Zeilen: gleicher Wert, unterschiedliche Interpretation.
Lösung zu Aufgabe 2
#include <stdio.h>
struct Profil {
char name[50];
int alter;
int anzahl_hobbys;
};
int main(void)
{
struct Profil profil = { "Marcel", 34, 3 };
printf("%s ist %d und hat %d Hobbys.\n", profil.name, profil.alter, profil.anzahl_hobbys);
return 0;
}Lösung zu Aufgabe 3
#include <stdio.h>
int main(void)
{
char text[20];
printf("char: %zu\n", sizeof(char)); // 1 – per Definition immer 1
printf("int: %zu\n", sizeof(int)); // 4 (üblich)
printf("long: %zu\n", sizeof(long)); // 8 auf 64-Bit-Linux/macOS, 4 auf Windows
printf("double: %zu\n", sizeof(double)); // 8
printf("char[20]: %zu\n", sizeof(text)); // 20 – Arrays kennen ihre Größe (noch!)
return 0;
}Dass long je nach Plattform unterschiedlich groß ist, ist genau der Grund für <stdint.h>.
Lösung zu Aufgabe 4
#include <stdio.h>
int main(void)
{
double summe = 0.10 + 0.10 + 0.10;
printf("%d\n", summe == 0.30); // 0 – falsch! Float-Rundungsfehler
printf("%.17f\n", summe); // 0.30000000000000004
int summe_in_cent = 10 + 10 + 10;
printf("%d\n", summe_in_cent == 30); // 1 – deshalb Geld immer in Cent als int
return 0;
}Lösung zu Aufgabe 5
#include <stdio.h>
#include <string.h>
int main(void)
{
char name[] = "Marcel";
printf("%s – sizeof: %zu, strlen: %zu\n", name, sizeof(name), strlen(name));
// Marcel – sizeof: 7, strlen: 6
name[3] = '\0';
printf("%s – sizeof: %zu, strlen: %zu\n", name, sizeof(name), strlen(name));
// Mar – sizeof: 7, strlen: 3
return 0;
}sizeof bleibt 7 – das Array ist noch genauso groß. Aber strlen und printf hören beim ersten '\0' auf.
Die Bytes 'c', 'e', 'l' liegen noch im Speicher – sie sind nur „unsichtbar“ geworden. Genau so funktionieren C-Strings.
Zusatz: Die Float-Falle
Und jetzt kommt eine Falle, in die wirklich jeder einmal reinläuft:
printf("%d\n", 0.1 + 0.2 == 0.3); // 0 – falsch!Ja, wirklich. Falsch.
Floats und Doubles sind binär gespeichert, und 0.1 ist binär ein unendlicher Bruch – wie 1/3 im Dezimalsystem. Das ist keine C-Schwäche, das ist IEEE 754 und betrifft praktisch jede Sprache.
Genau deshalb rechnet man Geldbeträge niemals mit Floats, sondern in Cent als int oder long.
Das ist keine Theorie. Das ist die Sorte Bug, die irgendwann in einer Rechnung landet.
Wie geht es weiter?
Das waren die Bausteine, aus denen jedes C-Programm besteht.
Im nächsten Teil geht es um Kontrollstrukturen – if, switch, Schleifen. Also darum, wie ein Programm Entscheidungen trifft.