Systempraktikum Vorlesung 4: Git, Debugging in C und weitere Themen


Einführung

In der vierten Vorlesung des Systempraktikums standen die Themen Versionsverwaltung mit Git, Debugging in C sowie weitere relevante Aspekte der Softwareentwicklung im Fokus. Der Schwerpunkt lag auf der praktischen Anwendung von Git, einschließlich grundlegender und fortgeschrittener Befehle, der Verwaltung von Branches und der Lösung von Merge-Konflikten. Zusätzlich wurden wesentliche Debugging-Methoden vorgestellt, die zur Verbesserung der Codequalität und zur effizienten Fehlerbehebung beitragen.


1. Versionsverwaltung mit Git

1.1 Grundlagen von Git

Git ist ein verteiltes Versionskontrollsystem, das den Verlauf von Codeänderungen verfolgt und eine effektive Zusammenarbeit im Team ermöglicht. Wichtige Konzepte umfassen:

  • Repository: Speichert den gesamten Projektcode sowie die Historie aller Änderungen. Kann lokal auf dem eigenen Rechner oder remote auf Plattformen wie GitHub oder GitLab gehostet werden.
  • Commit: Ein Schnappschuss des aktuellen Projektzustands. Jeder Commit enthält eine Nachricht, die die vorgenommenen Änderungen beschreibt.
  • Branch: Ermöglicht parallele Entwicklungen innerhalb eines Projekts, z.B. für neue Features oder Bugfixes, ohne den Hauptbranch (main oder master) zu beeinträchtigen.

1.2 Wichtige Git-Befehle

Folgende Git-Befehle wurden eingeführt und anhand praktischer Beispiele demonstriert:

  • git init: Initialisiert ein neues Git-Repository im aktuellen Verzeichnis.
  • git clone <URL>: Klont ein bestehendes Remote-Repository auf den lokalen Rechner.
  • git add <Datei>: Fügt Änderungen an Dateien zur Staging-Area hinzu, um sie für den nächsten Commit vorzubereiten.
  • git commit -m "Nachricht": Erstellt einen neuen Commit mit den in der Staging-Area befindlichen Änderungen und der angegebenen Nachricht.
  • git status: Zeigt den aktuellen Status des Repositories an, inklusive der Änderungen, die noch nicht gestaged oder committet wurden.
  • git log: Listet die Historie aller Commits im Repository auf.
  • git push: Überträgt lokale Commits zum Remote-Repository.
  • git pull: Holt und integriert Änderungen aus dem Remote-Repository in das lokale Repository.
  • git branch: Listet alle vorhandenen Branches auf oder erstellt neue Branches.
  • git checkout <Branch-Name>: Wechselt zu einem bestehenden Branch.
  • git merge <Branch-Name>: Führt einen bestehenden Branch in den aktuellen Branch zusammen.
  • git remote add origin <URL>: Fügt ein Remote-Repository hinzu.
  • git stash: Speichert aktuelle Änderungen temporär und setzt das Arbeitsverzeichnis zurück.
  • git reset --hard: Setzt das Repository auf einen bestimmten Commit zurück und verwirft alle Änderungen.

1.3 Praktische Anwendung von Git

Anhand eines Beispielprojekts wurde die praktische Anwendung von Git demonstriert:

  • Staging und Commit:

    Änderungen an Dateien werden mit git add <Datei> zur Staging-Area hinzugefügt. Ein Commit wird anschließend mit einer aussagekräftigen Nachricht erstellt, z.B.:

    git add verbindung.c main.c
    git commit -m "Verbindung und Hauptfunktion verbessert"
    gitGraph
       commit id: "Initial Commit"
       commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
    

    Erklärung:

    • Initial Commit: Grundlegender Commit des Projekts.
    • v1.1: Verbesserungen an Verbindung und Hauptfunktion.
  • Branching und Merging:

    Erstellung eines neuen Branches, Wechsel zu diesem Branch, Vornahme von Änderungen und anschließendes Zusammenführen zurück in den Hauptbranch:

    git branch feature/debugging
    git checkout feature/debugging
    git add debug.c
    git commit -m "Debugging-Funktionalität implementiert"
    git checkout main
    git merge feature/debugging
    gitGraph
       commit id: "Initial Commit"
       commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
       branch feature/debugging
       commit id: "Debugging-Funktionalität implementiert" tag: "feature/debugging"
       checkout main
       merge feature/debugging id: "Merge feature/debugging into main"
       commit id: "Nach Merge Commit"
    

    Erklärung:

    • feature/debugging: Branch zur Implementierung von Debugging-Funktionalitäten.
    • Merge: Zusammenführung des Feature-Branches zurück in den Hauptbranch.
    • Nach Merge Commit: Finaler Commit nach dem Merge.
  • Remote Repository hinzufügen und Pushen:

    git remote add origin https://github.com/username/repository.git
    git push -u origin main
     
gitGraph
    commit id: "Initial Commit"
    commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
    branch feature/debugging
    checkout feature/debugging
    commit id: "Debugging-Funktionalität implementiert" tag: "feature/debugging"
    checkout main
    merge feature/debugging id: "Merge feature/debugging into main"
    commit id: "Nach Merge Commit"
    commit id: "Änderungen zu origin/main gepusht"

Erklärung:

  • Remote Repository hinzufügen: Verknüpft das lokale Repository mit einem Remote-Repository auf GitHub.

  • Push: Überträgt die lokalen Commits zum Remote-Repository.

  • Stashing und Wiederherstellen von Änderungen:

    git stash
    # Änderungen sind nun gestasht und das Arbeitsverzeichnis ist sauber
    git stash apply
    # Gestashte Änderungen werden wiederhergestellt
    gitGraph
       commit id: "Initial Commit"
       commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
       commit id: "Änderung vor Stash"
       commit id: "Nach Stash"
       commit id: "Wiederhergestellte Änderung"
    

    Erklärung:

    • Änderung vor Stash: Änderungen, die gestasht werden sollen.
    • Nach Stash: Arbeitsverzeichnis nach dem Stashen ist sauber.
    • Wiederhergestellte Änderung: Änderungen werden wiederhergestellt und committet.

    Hinweis: Da die gitGraph-Syntax keine direkte Unterstützung für stash bietet, wird das Stashen und Wiederherstellen von Änderungen als separate Commits dargestellt.

1.4 Erweiterte Git-Konzepte

  • Merge-Konflikte:

    Entstehen, wenn Änderungen in verschiedenen Branches an denselben Stellen einer Datei vorgenommen werden. Die Lösung erfolgt durch manuelles Bearbeiten der betroffenen Dateien und anschließendes Commit.

    git merge feature/conflict
    # Konflikt entsteht
    # Manuelles Bearbeiten der Konfliktdatei
    git add konflikt_datei.c
    git commit -m "Konflikt behoben in konflikt_datei.c"
gitGraph
    commit id: "Initial Commit"
    commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
    branch feature/conflict
    checkout feature/conflict
    commit id: "Änderung in feature/conflict" tag: "feature/conflict"
    checkout main
    commit id: "Weitere Änderungen in main"
    merge feature/conflict id: "Merge feature/conflict into main"
    commit id: "Konflikt behoben in konflikt_datei.c"

Erklärung:

  • feature/conflict: Branch mit Änderungen, die zu einem Merge-Konflikt führen.

  • Konflikt: Manuelles Bearbeiten und Beheben des Konflikts in der betroffenen Datei.

  • Finaler Commit: Commit nach der Konfliktlösung.

  • Rebasing:

    Anpassung der Commit-Historie zur linearen Darstellung, was die Historie übersichtlicher macht. Einsatz von git rebase, um Commits von einem Branch auf einen anderen zu übertragen.

    git checkout feature/rebase
    git rebase main
gitGraph
    commit id: "Initial Commit"
    commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
    branch feature/rebase
    checkout feature/rebase
    commit id: "Änderung in feature/rebase" tag: "feature/rebase"
    checkout main
    commit id: "Weitere Änderungen in main"
    checkout feature/rebase
    commit id: "Änderung in feature/rebase nach Rebase" tag: "feature/rebase"

Erklärung:

  • feature/rebase: Feature-Branch, der später auf den main-Branch rebased wird.
  • Rebase: Anpassung der Commit-Historie zur linearen Darstellung.

1.5 Best Practices in Git

  • Sinnvolle Commit-Nachrichten:

    Nachrichten sollten prägnant und beschreibend sein, um die vorgenommenen Änderungen nachvollziehbar zu machen.

    git commit -m "Print access message hinzugefügt"
  • Regelmäßiges Committen:

    Häufige Commits erleichtern die Nachverfolgung von Änderungen und die Fehlerbehebung.

  • Branching-Strategien:

    Nutzung von Feature-Branches zur isolierten Entwicklung neuer Features. Integration von Branches nach erfolgreichem Testen und Review in den Hauptbranch.

  • .gitignore:

    Konfiguration von Dateien und Verzeichnissen, die von der Versionskontrolle ausgeschlossen werden sollen, um unnötige oder sensible Daten nicht zu committen.

    # Beispielhafte Einträge
    *.o
    *.exe
    node_modules/
    *.log
    *.tmp
    build/
gitGraph
    commit id: "Initial Commit"
    commit id: ".gitignore hinzugefügt" tag: "v1.2"

Erklärung:

  • .gitignore hinzugefügt: Commit, der die .gitignore-Datei hinzufügt, um bestimmte Dateien und Verzeichnisse auszuschließen.

1.6 Remote-Repositories und Zusammenarbeit

  • Einrichtung von Remote-Repositories:

    Nutzung von Plattformen wie GitHub oder GitLab zur Verwaltung und Zusammenarbeit an Projekten. Einrichtung von SSH-Keys zur Authentifizierung und sicheren Verbindung.

    git remote add origin https://github.com/username/repository.git
    git push -u origin main
gitGraph
    commit id: "Initial Commit"
    commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
    branch feature/debugging
    checkout feature/debugging
    commit id: "Debugging-Funktionalität implementiert" tag: "feature/debugging"
    checkout main
    merge feature/debugging id: "Merge feature/debugging into main"
    commit id: "Nach Merge Commit"
    commit id: "Änderungen zu origin/main gepusht"

Erklärung:

  • Remote Repository hinzufügen: Verknüpft das lokale Repository mit einem Remote-Repository auf GitHub.

  • Push: Überträgt die lokalen Commits zum Remote-Repository.

  • Zusammenarbeit im Team:

    Gemeinsames Arbeiten an Branches. Regelmäßiges Pullen und Pushen von Änderungen, um den aktuellen Stand des Projekts zu erhalten.

    git pull origin main
    git push origin feature/debugging
    gitGraph
       commit id: "Initial Commit"
       branch main
       commit id: ".gitignore hinzugefügt" tag: "v1.2"
       branch feature/debugging
       commit id: "Debugging-Funktionalität implementiert" tag: "feature/debugging"
       checkout main
       merge feature/debugging id: "Merge feature/debugging into main"
       commit id: "Nach Merge Commit"
       push origin main
       pull origin main
    

    Erklärung:

    • Pull: Holt die neuesten Änderungen vom Remote-Repository und integriert sie in das lokale Repository.
    • Push: Überträgt die lokalen Commits zum Remote-Repository.

2. Debugging in C

Das Debugging von C-Programmen ist ein essentieller Bestandteil der Softwareentwicklung zur Identifikation und Behebung von Fehlern im Code.

2.1 Wichtigkeit des Debuggings

  • Fehleridentifikation und -behebung:

    Debugging hilft dabei, logische Fehler und Laufzeitfehler im Code zu erkennen und zu korrigieren.

  • Verbesserung der Codequalität:

    Durch systematisches Debugging wird die Stabilität und Zuverlässigkeit der Anwendung erhöht.

2.2 Debugging-Tools

  • gdb (GNU Debugger):

    Ein leistungsfähiges Tool zur Fehlersuche in C-Programmen.

    Grundlegende Befehle:

    • run: Startet das Programm unter Debugging.
    • break <Funktion/Zeilennummer>: Setzt Haltepunkte im Code.
    • next und step: Navigieren durch den Code Schritt für Schritt.
    • print <Variable>: Zeigt den aktuellen Wert von Variablen an.

    Beispiel:

    #include <stdio.h>
     
    int main() {
        int a = 5;
        int b = 0;
        printf("Start Debugging\n");
        int c = a / b; // Fehler: Division durch Null
        printf("Ergebnis: %d\n", c);
        return 0;
    }
    # Debugging mit gdb
    gdb ./programm
    (gdb) break main
    (gdb) run
    (gdb) print a
    (gdb) print b
    (gdb) step
    graph TD
        A[Start Debugging] --> B{Division durch Null}
        B --> C[Fehler]
    

    Erklärung:

    • Start Debugging: Programmstart.
    • Division durch Null: Laufzeitfehler aufgrund der Division durch Null.
  • IDE-Integration:

    Viele Entwicklungsumgebungen bieten integrierte Debugging-Funktionen, die die Nutzung von gdb vereinfachen und visualisieren.

2.3 Best Practices im Debugging

  • Schrittweises Debugging:

    Testen von Codeabschnitten in kleinen Einheiten, um Fehlerquellen effizient einzugrenzen.

  • Logging:

    Einsatz von printf-Anweisungen zur Überwachung des Programmablaufs und der Variablenwerte zur Laufzeit.

  • Code-Reviews:

    Gemeinsame Durchsicht des Codes mit Kollegen zur frühzeitigen Identifikation von Fehlern und Verbesserungspotenzialen.

    Beispiel:

    #include <stdio.h>
     
    int main() {
        int a = 5;
        int b = 0;
        printf("Start Debugging\n");
        int c = a / b; // Fehler: Division durch Null
        printf("Ergebnis: %d\n", c);
        return 0;
    }
    # Debugging mit gdb
    gdb ./programm
    (gdb) break main
    (gdb) run
    (gdb) print a
    (gdb) print b
    (gdb) step
    graph TD
        A[Start Debugging] --> B{Division durch Null}
        B --> C[Fehler]
    

    Erklärung:

    • Start Debugging: Programmstart.
    • Division durch Null: Laufzeitfehler aufgrund der Division durch Null.

3. Weitere Themen

Neben Git und Debugging wurden auch weitere relevante Themen behandelt, die für die effiziente Entwicklung und Verwaltung von Softwareprojekten wichtig sind.

3.1 Versionsverwaltung in IDEs

  • Integration von Git in Entwicklungsumgebungen:

    Nutzung von Git-Integrationen in Entwicklungsumgebungen wie Visual Studio Code erleichtert das Management von Commits und Branches direkt aus der IDE heraus.

    graph LR
        IDE[Visual Studio Code] --> Git[Git Integration]
        Git --> Repository[Repository Management]
    

    Erklärung:

    • IDE: Entwicklungsumgebung, die Git integriert.
    • Git Integration: Ermöglicht die Nutzung von Git-Befehlen direkt aus der IDE.
    • Repository Management: Verwaltung von Repositories innerhalb der IDE.

3.2 Remote-Repositories

  • Nutzung von Plattformen wie GitHub oder GitLab:

    Ermöglicht die Zusammenarbeit mit anderen Entwicklern und das Teilen von Code. Einfache Verwaltung von Zugriffsrechten und Projekten.

    graph LR
        LocalRepo[Lokales Repository] --> RemoteRepo[GitHub/GitLab]
        RemoteRepo --> Collaborators[Kollaborate]
    

    Erklärung:

    • LocalRepo: Lokales Git-Repository.
    • RemoteRepo: Remote-Repository auf GitHub oder GitLab.
    • Collaborators: Teammitglieder, die am Projekt mitarbeiten.

3.3 .gitignore

  • Konfiguration zur Ausschließung von Dateien:

    Vermeidung des Commitens unnötiger oder sensibler Dateien durch die .gitignore-Datei.

    Beispielhafte Einträge:

    # Beispielhafte Einträge
    *.log
    *.tmp
    build/
    graph TD
        Files[Dateien] -->|Ignoriert| .gitignore
        Files -->|Nicht ignoriert| Repository
    

    Erklärung:

    • Ignoriert: Dateien, die in .gitignore aufgeführt sind, werden von Git ignoriert.
    • Nicht ignoriert: Dateien, die nicht in .gitignore aufgeführt sind, werden im Repository verfolgt.

4. Git History und Branches mit Mermaid

Um die Git-Historie und die Branch-Struktur visuell darzustellen, werden folgende Mermaid-Diagramme verwendet, die die behandelten Git-Commands und deren Auswirkungen veranschaulichen.

4.1 Git History

Das folgende Diagramm zeigt eine einfache Git-Historie mit mehreren Commits:

gitGraph
   commit id: "Initial Commit"
   commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"

Erklärung:

  • Initial Commit: Grundlegender Commit des Projekts.
  • v1.1: Verbesserungen an Verbindung und Hauptfunktion.

4.2 Git Branching

Das folgende Diagramm veranschaulicht das Erstellen und Zusammenführen von Branches:

gitGraph
   commit id: "Initial Commit"
   commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
   branch feature/debugging
   commit id: "Debugging-Funktionalität implementiert" tag: "feature/debugging"
   checkout main
   merge feature/debugging id: "Merge feature/debugging into main"
   commit id: "Nach Merge Commit"

Erklärung:

  • feature/debugging: Branch zur Implementierung von Debugging-Funktionalitäten.
  • Merge: Zusammenführung des Feature-Branches zurück in den Hauptbranch.
  • Nach Merge Commit: Finaler Commit nach dem Merge.

4.3 Umgang mit Merge-Konflikten

Das folgende Diagramm zeigt den Ablauf bei einem Merge-Konflikt und dessen Lösung:

gitGraph
    commit id: "Initial Commit"
    commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
    branch feature/conflict
    checkout feature/conflict
    commit id: "Änderung in feature/conflict" tag: "feature/conflict"
    checkout main
    commit id: "Weitere Änderungen in main"
    merge feature/conflict id: "Merge feature/conflict into main"
    commit id: "Konflikt behoben in konflikt_datei.c"

Erklärung:

  • feature/conflict: Branch mit Änderungen, die zu einem Merge-Konflikt führen.
  • Konflikt: Manuelles Bearbeiten und Beheben des Konflikts in der betroffenen Datei.
  • Finaler Commit: Commit nach der Konfliktlösung.

4.4 Stashing und Wiederherstellen

Das folgende Diagramm zeigt das Stashen von Änderungen und deren Wiederherstellung:

gitGraph
   commit id: "Initial Commit"
   commit id: "Verbindung und Hauptfunktion verbessert" tag: "v1.1"
   commit id: "Änderung vor Stash"
   commit id: "Nach Stash"
   commit id: "Wiederhergestellte Änderung"

Erklärung:

  • Änderung vor Stash: Änderungen, die gestasht werden sollen.
  • Nach Stash: Arbeitsverzeichnis nach dem Stashen ist sauber.
  • Wiederhergestellte Änderung: Änderungen werden wiederhergestellt und committet.

Hinweis: Da die gitGraph-Syntax keine direkte Unterstützung für stash bietet, wird das Stashen und Wiederherstellen von Änderungen als separate Commits dargestellt.


Zusammenfassung

Die vierte Vorlesung des Systempraktikums vermittelte ein tiefgehendes Verständnis der Versionsverwaltung mit Git sowie der Debugging-Techniken in C. Der Schwerpunkt lag auf der praktischen Anwendung von Git, einschließlich der Nutzung grundlegender und fortgeschrittener Befehle, der Verwaltung von Branches und der Lösung von Merge-Konflikten. Zusätzlich wurden wesentliche Debugging-Methoden vorgestellt, die zur Verbesserung der Codequalität und zur effizienten Fehlerbehebung beitragen. Durch die praxisorientierte Herangehensweise und die Besprechung von Best Practices sind die Studierenden in der Lage, effektiv in Teamprojekten zu arbeiten und ihre Programmierfähigkeiten weiterzuentwickeln.


Weiterführende Ressourcen