C-Grundlagen Teil 2 – Typen in C verstehen

int, char, float, double und die Wahrheit über Strings: Warum Typen in C nur eine Anleitung zum Interpretieren von Bytes sind – mit Übungsaufgaben zum Selbermachen.

C

C-Grundlagen Teil 2 – Typen in C verstehen

Marcel Strahl5 Min. Lesezeit

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 Kommazahlen

Wichtig: Der Dezimaltrenner ist ein Punkt, kein Komma. Und: Kommazahlen-Literale sind in C standardmäßig doublefloat 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-Byte

Und 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 passiert

Die 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 aus

Zeile 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.

  • 5 Min. LesezeitRust

    Rust-Grundlagen Teil 2 – Typen in Rust verstehen

    Unveränderlichkeit als Standard, keine impliziten Umwandlungen, Option statt null und der Unterschied zwischen &str und String: die Grundlagen des Rust-Typsystems – mit Übungsaufgaben zum Selbermachen.

    • #Rust
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Systemprogrammierung
  • 5 Min. LesezeitPHP

    PHP-Grundlagen Teil 2 – Typen in PHP verstehen

    Welche Datentypen es in PHP gibt, wofür man sie wirklich braucht und warum strict_types von Anfang an gesetzt gehört – mit Übungsaufgaben zum Selbermachen.

    • #PHP
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Webentwicklung
    • #Backend
  • 5 Min. LesezeitC#

    C#-Grundlagen Teil 2 – Typen in C# verstehen

    Value Types, Reference Types, Nullable Reference Types und warum decimal für Geld gemacht ist: die Grundlagen des C#-Typsystems – mit Übungsaufgaben zum Selbermachen.

    • #C#
    • #Grundlagen
    • #Tutorial
    • #Software-Entwicklung
    • #Backend