Zum Inhalt

C

📅 Erstellt: 18.04.2026 🔄 Aktualisiert: 23.08.2026

C Programmierung - Gedanken und Notizen

Strings

Hier geht es um den Unterschied zwischen strcmp() und strstr()


Ein Gedanke

strcmp() vergleicht zwei komplette Strings miteinander.
strstr() sucht einen Teilstring in einem anderen String.

Sie haben also völlig unterschiedliche Aufgaben.


Erklärung

1) Was macht strcmp()?

👉 Vergleicht zwei ganze Strings alphabetisch.

Ergebnis:

  • < 0 → String A kommt vor String B
  • > 0 → String A kommt nach String B
  • 0 → beide sind gleich

Beispiel:

strcmp("Apfel", "Banane")    < 0
strcmp("Zitrone", "Apfel")   > 0
strcmp("Hund", "Hund")       0

Wichtig:
strcmp() liefert kein Boolean, sondern eine Sortierreihenfolge.


2) Was macht strstr()?

👉 Sucht einen Teilstring in einem anderen String.

Ergebnis:

  • Pointer → wenn gefunden
  • NULL → wenn nicht gefunden

Beispiel:

strstr("Laptop", "top")    zeigt auf "top"
strstr("Laptop", "ap")     zeigt auf "aptop"
strstr("Laptop", "xyz")    NULL

Wichtig:
strstr() liefert keinen Vergleich, sondern eine Fundstelle.


3) Warum wirken beide wie „Boolean“?

Weil du sie so benutzt:

if (strcmp(...) > 0)   // true/false
if (strstr(...) != NULL)

Aber:

  • strcmp() → liefert Zahlen (negativ, 0, positiv)
  • strstr() → liefert Pointer oder NULL

Du machst daraus ein Boolean, aber sie sind keine Boolean‑Funktionen.


Module

Unter anderem aus dem Buch C Programmieren für Einsteiger - Der leichte Weg zum C-Experten - 2020, BMU Verlag

Hier auf o.g. Buch bezogen Kapitel 9:


Definition

Module trennen Programme in klar definierte, wiederverwendbare Einheiten. Sie verbessern Übersicht, Wartbarkeit, Zusammenarbeit und ermöglichen es, große Projekte sauber zu strukturieren.

Ein Modul ist:

  • eine funktionale Einheit,

  • die einen Dienst anbietet (z. B. Mathefunktionen, Textfunktionen, Datenbankfunktionen),

  • aber kein eigenständiges Programm ist.

  • Es wird immer von einem Hauptprogramm (meist main.c) genutzt.

Typischer Aufbau eines Moduls in C

Ein Modul besteht üblicherweise aus zwei Dateien:

  1. Header-Datei (.h)
    → enthält das Interface: Funktionsdeklarationen, Typdefinitionen, Konstanten

  2. Implementierungsdatei (.c)
    → enthält die Funktionsdefinitionen (also den eigentlichen Code)

Beispiel:

  • mathe.h
  • mathe.c

Das Hauptprogramm (main.c) bindet das Modul ein über:

#include "mathe.h"


Warum Module verwenden?

  1. Bessere Übersichtlichkeit
    Große Programme können hunderte oder tausende Zeilen haben.
    Durch Module wird der Code thematisch sortiert und leichter zu verstehen.

  2. Bessere Zusammenarbeit
    Mehrere Entwickler können parallel arbeiten, ohne sich gegenseitig in die Quere zu kommen.

  3. Einfachere Wartung
    Änderungen müssen nur in einem Modul vorgenommen werden, nicht im ganzen Projekt.

  4. Wiederverwendbarkeit
    Ein einmal geschriebenes Modul kann in anderen Projekten wiederverwendet werden, ohne den Code neu zu schreiben.


Grafische Übersicht

Bereich Datei / Element Erklärung
📁
Projektordner
main.c
-> Hauptprogramm (startet alles)
modulname.h
-> Header-Datei (Interface)
Funktionsdeklarationen
Konstanten / Typen
Wird von main.c eingebunden
modulname.c
-> Implementierung
#include "modulname.h"
Funktionsdefinitionen
Interner Code des Moduls
Beispiel "Matheprojekt"
📁 MatheProjekt
│
├── main.c
│
├── mathe.h
│
└── mathe.c
Die einzlnen Dateien
mathe.h:
int addieren(int a, int b);
int multiplizieren(int a, int b);

---

mathe.c:
#include "mathe.h"

int addieren(int a, int b) {
    return a + b;
}

int multiplizieren(int a, int b) {
    return a * b;
}

---

main.c:
#include <stdio.h>
#include "mathe.h"

int main() {
    printf("%i\n", addieren(3, 4));
    return 0;
}
Kompilieren
gcc main.c mathe.c -o programm

Verwendung in VS Code

Das ist mir noch nicht abschließend klar, nutze dort CMake und Tools der Extension. Ist aber für mich noch recht clunky zu bedienen :-/
Evtl. hier den simpleren Makefile Ansatz gehen?
Oder ganz oldskool eine Bash-/Batchdatei erstellen mit den nötigen Parametern?! <- Das kann es aber 2026 mit VS Code nicht sein, oder??


Zeiger

Bei x = 5, p = &x gilt:
p ist die Adresse (z.B. 0x1000)
*p ist der INHALT der Adresse (5)
p zeigt auf x

* bedeutet "geh zu dem, worauf gezeigt wird"

Bei der Deklaration eines Zeigers gebe ich ihm für den Compiler ein "Etikett". Dann weiss der Compiler z.B. bei int *p;, um wieviel Bytes er bei p++; springen muss. Integer haben 4 Bytes, soweit ich weiss.

Und wie der Compiler p interpretieren soll:
Wenn man int *p; schreibt, weiss der Compiler, dass es sich hier bei p um eine Adresse handelt.
Dass *p der Wert an der Adresse ein Integer ist. Oder anders gesagt: die Adresse zeigt auf einen
int
Ohne die Deklaration int *p; wäre
p = 10 illegal, weil p kein Zeiger wäre!

Für *p = 10 gilt, wenn p = 0x1000 ist: Schreibe 10 nach Adresse 0x1000
p (ja, p, nicht *p!) ist kein Wert, sondern ein Ort im Speicher!

p++ erhöht die Adresse, z.B. um sizeof(int) = 4 Bytes -> 0x1000 auf 0x1004, p soll also zum nächsten Element gehen
Alternativ: p++ = p + sizeof(Typ), wenn p ein char* wäre, wäre es +1, bei double* +8, int* +4

Sinnvoll bei einem Array:

int arr[3] = {10, 20, 30};

Adresse:
0x1000  10
0x1004  20
0x1008  30

p = arr;   // p = 0x1000
p++;       // p = 0x1004
p++;       // p = 0x1008

ACHTUNG!
p += 3 bedeutet nicht +3 Bytes, sondern +3 * sizeof(int)!
short *p;: p += 5 p = 0x9000 -> 0x900A, da sizeof(short) = 2 Bytes sind.

Zusammenfassung:
p++ gehe ein Element weiter
p += n gehe n Elemente weiter
p-- gehe ein Element zurück
p -= n gehe n Elemente zurück
a[b] == *(a + b) Syntaktischer Zucker
p = array Zurücksetzen der Adresse auf Anfang des Arrays

Kleine scanf() Falle:

scanf("%i", &zahl);     // richtig
scanf("%f", &wert);     // richtig
scanf("%s", name);      // richtig
scanf("%s", &name);     // falsch

Hier mal ein Beispiel für Adresse und Wert-Unterscheidung:

#include <stdio.h>

int main(void)
{
    // int-Variable deklarieren
    int var = 16;

    // Zeiger auf die Variable 'var' setzen
    int *p = &var;

    // Adresse und Wert ausgeben
    printf("\nDie Adresse ist:      %p", p);
    printf("\nDer Wert ist:         %d", *p);

    // Wert über Zeiger ändern
    *p = 17;
    printf("\nDie NEUE Adresse ist: %p", p);
    printf("\nDer NEUE Wert ist:    %d", *p);

    // Alles ok, Ende mit 0
    return 0;
}

Die Ausgabe des Programms ist:

Die Adresse ist:      0x7fffffffdcac
Der Wert ist:         16
Die NEUE Adresse ist: 0x7fffffffdcac
Der NEUE Wert ist:    17⏎   


Detailliert im Array

int arr[5] = {10, 20, 30, 40, 50};

arr         ->  Adresse des ersten Elements
&arr[0]     ->  gleiche Adresse (erstes Element)
*arr        ->  Wert des ersten Elements (10)

arr + 1     ->  Adresse des zweiten Elements
*(arr + 1)  ->  Wert des zweiten Elements (20)

Oder: arr[i] == *(arr + i)

int *p = arr;
p zeigt auf arr[0], also 10 usw.

Wenn du in der Funktion ausgabeFeld() schreibst:

dFeld[0]

… dann bedeutet das:
„Nimm die Adresse, die in dFeld steht, und lies den Wert am ersten Element des ursprünglichen Arrays.“

Mini‑Merksatz:

Ohne Stern → Adresse  
Mit Stern  → Wert

Das ist die goldene Regel.

ACHTUNG ACHTUNG!

/*
 * Bei int var = 16;
 * und int feld[5] = {...}; 
 */

int *p = &var;     // Einzelvariable → & nötig
int *p = feld;     // Array → feld ist schon Adresse


Funktionspointer

36c8692ab7f0ca494d7d6f6b8efa6db3.png

add:   [Maschinencode für add]
        ^
        |
       fp  ---->  (*fp)(2,3)

9da338a1549de77bd5c80ac7345cd6ea.png
Pointer-Mechanik:
cdbc2e2eee81d9660243e479242058d9.png
Code-Beispiel:
... 
void quadrat(int* zahl);

int main() {
    ...
    quadrat(&zahl);
    ...
}

void quadrat(int *p) {
    *p = *p * *p;
}


Dynamische Speicherbelegung

malloc() und calloc() unterscheiden sich in ihrer Geschwindigkeit und Methode.
Beide reservieren Speicher, der reservierte Speicher bei malloc() ist mit Resten, der im Speicher ist belegt.

calloc() initialisiert JEDES Feld bei der Reservierung mit 0. Daher ist calloc() langsamer, aber sauber(er).

realloc() verschiebt den Speicher mit den alten Daten an eine neue Stelle, die Anzahl der Elemente kann auch verändert werden. Eine Vergrößerung initialisiert die erweiterten Daten NICHT mit 0.
Der alte Speicher wird freigegeben.
Der Zeiger muss dann mit zeiger = array; wieder zurückgesetzt werden.

array ----> [5][7][15]
zeiger -----^

array ----> [5][7][15][...viel mehr Platz...]
zeiger ----X   (zeigt noch auf die alte Adresse → ungültig)

free() gibt den Speicher frei, aber NICHT den Zeiger!
Best practice:

free(array);
array = NULL;


Wenn VS Code mal Mist baut

Habe mal ausprobiert die dos-dev Erweiterung zu installieren, da hat er mir den ganzen Workspace versaut:
IntelliSense ungefragt verändert, default Compiler ebenso, die Ordnerstruktur mit 60 Files + Ordner vermüllt, clangd Sense eingestellt oder sowas? und einfach launch.json etc. überschrieben...

Habe dann den ganzen Ordner gelöscht und dann mit git in Kommandozeile das Repo neu geclont:
git clone git@github.com:DeinNutzername/c-code.git repo-clean

Das legt ein neues Verzeichnis namens repo-clean an, das verschiebe ich dann wieder auf den ursprünglichen Namen C Code

clangd habe ich ebenfalls deaktiviert, jetzt muss ich nur noch rausfinden wie ich IntelliSense wieder einschalte... -.-

Über die UI Settings komme ich an IntelliSense ran und habe es dort auf Default gestellt. Hmm...
Habe jetzt etwas der Workspace Datei hinzugefügt, um das festzulegen:

C Code.code-workspace:

{
    "folders": [
        {
            "path": "."
        }
    ],
    "settings": {
        "editor.tabCompletion": "on",
        "editor.snippetSuggestions": "top",
        "files.associations": {
            "*.c": "c"
        },

        // C/C++ IntelliSense — force it on, never let extensions change it
        "C_Cpp.intelliSenseEngine": "default",
        "C_Cpp.autocomplete": "default",
        "C_Cpp.errorSquiggles": "enabled",

        // Protect general editor intelligence from being disabled
        "editor.quickSuggestions": {
            "other": "on",
            "comments": "off",
            "strings": "off"
        },
        "editor.suggestOnTriggerCharacters": true,
        "editor.parameterHints.enabled": true,

        // Prevent extensions from overriding file associations
        "workbench.editorAssociations": {
            "*.c": "default"
        }
    }
}




Temporärer Speicher

Alles klar, G. — wir bleiben im Study‑Mode, also bekommst du jetzt 5 Übungsaufgaben, von leicht → schwer, perfekt abgestimmt auf das, was du gerade lernst:
Zeiger allgemein + Zeiger in Funktionen.

Und wie immer: keine Lösungen, nur Aufgaben — du sollst selbst denken.
Wenn du eine davon lösen willst, sag einfach Bescheid, und wir gehen sie gemeinsam durch.


1) Leicht — Adresse & Wert unterscheiden

Schreibe ein kleines Programm, das:

  • eine int‑Variable deklariert
  • einen Zeiger darauf setzt
  • die Adresse und den Wert ausgibt
  • den Wert über den Zeiger verändert
  • erneut Adresse und Wert ausgibt

Ziel:
Verstehen, was Adresse und was Wert ist.


2) Leicht/Mittel — Pointer‑Arithmetik

Gegeben:

int feld[5] = {10, 20, 30, 40, 50};
int *p = feld;

Schreibe ein Programm, das:

  • mit p durch das Array läuft
  • ohne feld[i]
  • nur mit *(p + i) arbeitet
  • alle Werte ausgibt

Ziel:
Pointer‑Arithmetik sicher anwenden.


3) Mittel — Funktion mit Zeigerparameter

Schreibe eine Funktion:

void verdopple(int *wert);

Die Funktion soll:

  • den Wert der übergebenen Variable verdoppeln
  • also *wert = *wert * 2;

In main():

  • eine int‑Variable anlegen
  • die Funktion aufrufen
  • vorher/nachher ausgeben

Ziel:
Zeiger an Funktionen übergeben und Werte verändern.


4) Mittel/Schwer — Array in Funktion füllen

Schreibe eine Funktion:

void fuelleArray(int *feld, int n);

Die Funktion soll:

  • das Array mit den Zahlen 1 bis n füllen
  • ohne Array‑Indexierung
  • nur mit Zeigerarithmetik (*(feld + i))

In main():

  • ein Array deklarieren
  • die Funktion aufrufen
  • das Ergebnis ausgeben

Ziel:
Zeiger + Schleifen + Funktionen kombinieren.


5) Schwer — Minimum, Maximum, Mittelwert

Schreibe drei Funktionen:

int minimum(int *feld, int n);
int maximum(int *feld, int n);
float mittelwert(int *feld, int n);

Alle drei sollen:

  • nur mit Zeigern arbeiten
  • keine Array‑Indexierung verwenden
  • also nur *(feld + i)

In main():

  • ein Array mit beliebigen Werten anlegen
  • alle drei Funktionen aufrufen
  • Ergebnisse ausgeben

Ziel:
Komplexere Logik mit Zeigern lösen.


⭐ Mini‑Frage (Study‑Mode)

Welche Aufgabe willst du zuerst machen?

A) Aufgabe 1 (Adresse & Wert)
B) Aufgabe 2 (Pointer‑Arithmetik)
C) Aufgabe 3 (Zeiger an Funktion)
D) Aufgabe 4 (Array füllen)
E) Aufgabe 5 (Min/Max/Mittelwert)

Nur A–E.