Programmierung in C – Teil II: Umfassende Zusammenfassung für Einsteiger
Diese Zusammenfassung richtet sich an C-Anfänger und bietet einen ausführlichen Überblick über die Inhalte des zweiten Teils der Vorlesung “Programmierung in C”. Ziel ist es, die Konzepte verständlich und strukturiert zu vermitteln, sodass auch Laien einen Einstieg in die Materie finden.
Kurzzusammenfassung
Vom Quelltext zur ausführbaren Datei:
Übersetzungsprozess:
Präprozessor verarbeitet Direktiven wie #include, #define.
Compiler übersetzt den präprozessierten Code in Assemblercode.
Assembler wandelt Assemblercode in Objektdateien (.o) um.
Linker verbindet Objektdateien und Bibliotheken zu einer ausführbaren Datei.
Bibliotheken werden während des Linkens eingebunden.
Die GNU Compiler Collection (GCC):
Führt standardmäßig alle Phasen des Übersetzungsprozesses aus.
Wichtige Optionen:
-c: Nur Kompilieren, keine Verlinkung.
-E: Stoppt nach dem Präprozessor.
-S: Erzeugt Assemblercode.
-o <Dateiname>: Legt den Namen der Ausgabedatei fest.
-Wall -Wextra: Aktiviert ausführliche Warnungen.
-g: Fügt Debugging-Informationen hinzu.
-std=c99: Legt den zu verwendenden C-Standard fest.
Modularisierung in C:
Ziel: Aufteilung in unabhängige Module mit klar definierten Schnittstellen.
Vorteile:
Erhöhte Übersichtlichkeit und Wartbarkeit.
Erleichtert Zusammenarbeit und Wiederverwendung von Code.
Headerdateien (.h):
Enthalten Deklarationen von Funktionen, Variablen, Makros und Typen.
Werden mit #include in Quellcodedateien eingebunden.
Include Guards:
Verhindern Mehrfacheinbindungen.
Syntax:
#ifndef DATEINAME_H#define DATEINAME_H// Inhalt der Headerdatei#endif
Alternative: #pragma once (einfachere Syntax, aber nicht standardisiert).
Erstellen eigener Headerdateien:
Deklaration von Funktionen und Makros in der Headerdatei.
Definition der Funktionen in der entsprechenden .c-Datei.
Beispiel:
geometrie.h: Enthält Funktionsprototypen und Makros.
geometrie.c: Implementiert die Funktionen.
main.c: Nutzt die Funktionen aus geometrie.h.
Bibliotheken in C:
Statische Bibliotheken (.a):
Werden zur Kompilierzeit in das Programm eingebunden.
Erstellung mit ar rcs libname.a objektdateien.
Dynamische Bibliotheken (.so):
Werden zur Laufzeit geladen.
Erstellung mit gcc -shared -o libname.so objektdateien.
Einbindung von Bibliotheken:
Beim Kompilieren mit -L<Pfad> und -l<name> angeben.
Beispiel: gcc main.c -L. -lmymath -o main.
LD_LIBRARY_PATH: Umgebungsvariable für dynamische Bibliothekenpfade.
Typische Linkerfehler und Behebung:
Fehlende Implementierung:
Fehler: undefined reference to 'funktion'.
Lösung: Fehlende .c-Datei beim Kompilieren angeben.
Fehlende Bibliothek:
Fehler: cannot find -lbibliothek.
Lösung: Korrekte Pfadangabe mit -L und Bibliotheksname mit -l.
Makefiles und Automatisierung:
Zweck: Automatisieren das Kompilieren und Linken von Programmen.
Grundstruktur:
ziel: abhaengigkeiten \<Befehl\>
Beispiel-Makefile:
Verwendung von Variablen (CC, CFLAGS).
Definition von Zielen (all, clean).
Regeln für das Erstellen von Objektdateien und der ausführbaren Datei.
Prozesse in Unix/Linux:
Definition: Ein in Ausführung befindliches Programm mit eigenen Ressourcen.
Prozesshierarchie:
Jeder Prozess (außer init) hat einen Elternprozess.
Waisenprozesse: Elternprozess beendet, Prozess wird von init adoptiert.
Zombieprozesse: Beendet, aber nicht von Elternprozess abgeholt.
Prozesskontrolle mit fork():
pid_t fork(void); erzeugt einen Kindprozess.
Rückgabewerte:
Elternprozess: PID des Kindprozesses (> 0).
Kindprozess: 0.
Fehlerfall: -1.
Verhalten:
Kindprozess ist Kopie des Elternprozesses.
Beide Prozesse setzen Ausführung nach fork() fort.
Beispiele:
Mehrfaches fork() erhöht die Anzahl der Prozesse exponentiell.
Fallunterscheidung anhand des Rückgabewerts ermöglicht unterschiedliche Logik.
Die exec-Familie von Funktionen:
Zweck: Ersetzen des aktuellen Prozesses durch ein anderes Programm.
Varianten: execl(), execv(), execvp(), etc.
Verwendung:
Wird häufig nach einem fork() im Kindprozess aufgerufen.
Rückgabeverhalten:
Bei Erfolg kehrt die Funktion nicht zurück.
Bei Fehler Rückgabe von -1.
Beispiel:
Kindprozess ersetzt sich selbst durch Aufruf von execlp() und führt ein anderes Programm aus.
Threads in C:
Definition: Leichtgewichtige Prozesse innerhalb eines Prozesses.
Unterschied zu Prozessen:
Teilen sich den gleichen Adressraum und Ressourcen.
In der Programmierung ist es wichtig zu verstehen, wie aus dem geschriebenen Quellcode letztendlich eine ausführbare Datei entsteht. Dieser Prozess umfasst mehrere Schritte:
Quellcode: Der vom Programmierer geschriebene Code in .c-Dateien.
Präprozessor: Verarbeitet Direktiven wie #include und #define und modifiziert den Quellcode entsprechend.
Übersetzer (Compiler): Wandelt den präprozessierten Code in Maschinensprache um.
Assembler: Übersetzt eventuellen Assemblercode in Objektcode.
Linker: Verbindet die Objektdateien mit notwendigen Bibliotheken zu einer ausführbaren Datei.
GCC ist eine weit verbreitete und leistungsfähige Compiler-Sammlung, die viele Programmiersprachen und Prozessorarchitekturen unterstützt. Für die Programmierung in C ist gcc das zentrale Werkzeug.
Phasen des Übersetzungsvorgangs
Präprozessor: Führt Makroersetzungen durch und inkludiert Headerdateien.
Compiler: Übersetzt den präprozessierten Code in Assemblercode.
Assembler: Wandelt den Assemblercode in Objektcode (.o-Dateien) um.
Linker: Verbindet Objektdateien und Bibliotheken zu einer ausführbaren Datei.
Anmerkungen:
Nicht alle Phasen müssen bei jedem Aufruf von gcc durchlaufen werden. Es gibt Optionen, um den Prozess an bestimmten Punkten zu stoppen oder nur bestimmte Phasen auszuführen.
GCC-Optionen und Parameter
Kompilieren ohne Linken: gcc -c erzeugt nur Objektdateien.
Präprozessor-Ergebnisse anzeigen: gcc -E stoppt nach dem Präprozessor.
Assemblercode erzeugen: gcc -S stoppt nach der Übersetzung in Assemblercode.
Typische Parameter:
Ausgabedatei angeben: -o <Dateiname> (Standard ist a.out).
Warnungen aktivieren: -Wall -Wextra schaltet umfassende Warnungen ein.
Debugging-Informationen einbinden: -g ermöglicht das Debuggen mit Tools wie gdb.
C-Standard festlegen: -std=c99 oder -std=c11 legt den zu verwendenden Sprachstandard fest.
Modularisierung ist ein Schlüsselkonzept in der Softwareentwicklung. Es ermöglicht die Aufteilung eines Programms in kleinere, überschaubare und wiederverwendbare Einheiten.
Headerdateien und ihre Verwendung
Headerdateien (.h-Dateien) enthalten Deklarationen von Funktionen, Variablen, Makros und Typen.
Sie dienen als Schnittstelle zwischen verschiedenen Modulen eines Programms.
Einbinden von Headerdateien:
Systemheader: #include <stdio.h> sucht im Systempfad.
Benutzerdefinierte Header: #include "meinHeader.h" sucht im aktuellen Verzeichnis oder im angegebenen Pfad.
Beispiele für Standard-Headerdateien:
stdio.h: Standard-Ein- und Ausgabefunktionen.
stdlib.h: Funktionen für Speicherverwaltung und Prozesssteuerung.
Schützen vor mehrfacher Einbindung einer Headerdatei.
Syntax:
#ifndef DATEINAME_H#define DATEINAME_H// Inhalt der Headerdatei#endif
Funktionsweise: Wenn das Makro (z. B. DATEINAME_H) nicht definiert ist, wird es definiert und der Inhalt der Headerdatei eingebunden. Andernfalls wird der Inhalt übersprungen.
#pragma once
Alternative zu Include Guards.
Verwendung: Einfach am Anfang der Headerdatei #pragma once hinzufügen.
Hinweis: Nicht Teil des C-Standards, aber von den meisten Compilern unterstützt.
Vorteil: Weniger fehleranfällig, da keine Makronamen verwaltet werden müssen.
Bibliotheken in C
Bibliotheken sind Sammlungen von Funktionen und/oder Variablen, die von verschiedenen Programmen gemeinsam genutzt werden können.
Statische Bibliotheken
Dateiendung: .a (Archivdateien).
Merkmale:
Werden zur Compile-Zeit in das Programm eingebunden.
Führt zu größeren ausführbaren Dateien, da der Code der Bibliothek integriert wird.
Keine Abhängigkeit zur Laufzeit.
Erstellung einer statischen Bibliothek:
Objektdateien erstellen:
gcc -c geometrie.c algebra.c
Bibliothek erstellen:
ar rcs libmymath.a geometrie.o algebra.o
ar: Archivierungswerkzeug.
rcs: Optionen zum Erstellen oder Aktualisieren eines Archivs.
Dynamische Bibliotheken
Dateiendung: .so (Shared Objects).
Merkmale:
Werden zur Laufzeit geladen.
Führt zu kleineren ausführbaren Dateien.
Ermöglicht das Teilen von Bibliotheken zwischen mehreren Programmen.
Hinweis: Der Linker bevorzugt standardmäßig dynamische Bibliotheken.
Pfad angeben: Mit -L<Pfad> wird der Pfad zu den Bibliotheken angegeben.
Bibliotheksname: Mit -l<Library> wird der Name der Bibliothek angegeben (ohne lib-Präfix und Dateiendung).
Laufzeitumgebung:
LD_LIBRARY_PATH: Umgebungsvariable, die zusätzliche Pfade für dynamische Bibliotheken zur Laufzeit angibt.
Überprüfung: Mit dem Befehl file <Datei> kann überprüft werden, ob es sich um eine statische oder dynamische Bibliothek handelt.
Typische Linkerfehler und deren Behebung
Fehlende Implementierung
Fehlermeldung:
/usr/bin/ld: main.c:(.text+0x80): undefined reference to `kreisflaeche'
collect2: error: ld returned 1 exit status
Ursache:
Die Funktion kreisflaeche wurde zwar deklariert, aber nicht definiert (die Implementierung fehlt).
Lösung:
Sicherstellen, dass die Implementierungsdatei (geometrie.c) beim Kompilieren angegeben wird:
gcc main.c geometrie.c -o main
Fehlende Bibliothek
Fehlermeldung:
/usr/bin/ld: cannot find -lmymath: No such file or directory
collect2: error: ld returned 1 exit status
Ursache:
Die angegebene Bibliothek libmymath.a oder libmymath.so wurde nicht gefunden.
Lösung:
Den Pfad zur Bibliothek mit -L angeben:
gcc main.c -L. -lmymath -o main
Überprüfen, ob die Bibliothek im angegebenen Verzeichnis vorhanden ist.
Makefiles und Automatisierung
Makefiles sind ein Werkzeug zur Automatisierung des Kompilierungsprozesses. Sie ermöglichen es, nur die tatsächlich notwendigen Dateien neu zu kompilieren, basierend auf definierten Abhängigkeiten.
Grundlagen von Makefiles
Aufbau:
Ziel: Abhängigkeiten <TAB> Befehl
Regeln:
Ziel: Das zu erzeugende Artefakt (z. B. eine ausführbare Datei oder eine Objektdatei).
Abhängigkeiten: Dateien, von denen das Ziel abhängt (z. B. Quellcode oder Headerdateien).
Befehl: Der auszuführende Befehl, um das Ziel zu erzeugen.
Variablen: Können definiert und im Makefile verwendet werden, um Wiederholungen zu vermeiden.
Aufruf:
make [<Ziel>]
Ohne Angabe eines Ziels wird das erste Ziel im Makefile ausgeführt.
Laden eines neuen Programms in den aktuellen Prozess.
Der aktuelle Prozess wird ersetzt, es kehrt nicht zur aufrufenden Funktion zurück.
Rückgabewert:
Bei Erfolg: Kehrt nicht zurück.
Bei Fehler: -1.
Beispiel: Ausführen eines anderen Programms
printargs.c
#include <stdio.h>#include <stdlib.h>int main(int argc, char **argv) { for (int i = 1; i < argc; i++) { printf("%d. Argument: %s\n", i, argv[i]); } return EXIT_SUCCESS;}
Funktion: Gibt die übergebenen Argumente aus.
exec.c
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/wait.h>int main(void) { pid_t pid; switch (pid = fork()) { case -1: perror("Fehler bei fork()"); return EXIT_FAILURE; case 0: // Kindprozess ersetzt sich selbst durch printargs execlp("./printargs", "./printargs", "a", "b", "c", NULL); perror("Fehler bei execlp()"); exit(EXIT_FAILURE); default: // Elternprozess wartet auf das Ende des Kindprozesses if (waitpid(pid, NULL, 0) != pid) { perror("Fehler beim Warten auf Kindprozess"); return EXIT_FAILURE; } } return EXIT_SUCCESS;}
Erklärungen:
fork(): Erzeugt einen Kindprozess.
Im Kindprozess:
execlp(): Ersetzt den Kindprozess durch das Programm printargs mit den Argumenten a, b, c.
Wichtig: Bei Erfolg kehrt execlp() nicht zurück.
Im Elternprozess:
waitpid(): Wartet darauf, dass der Kindprozess beendet wird.
Threads in C
Threads ermöglichen parallele Ausführung innerhalb eines Prozesses.
Unterschied zwischen Prozessen und Threads
Prozess:
Eigener Adressraum.
Ressourcen werden nicht standardmäßig geteilt.
Kommunikation über Interprozesskommunikation (IPC) notwendig.
Thread:
Gemeinsamer Adressraum innerhalb eines Prozesses.
Ressourcen wie Variablen und Dateien werden geteilt.
Effizienter bei Kontextwechseln.
POSIX-Threads (pthread)
Bibliothek: pthread.h
Voraussetzung: Beim Kompilieren muss die pthread-Bibliothek eingebunden werden (-pthread).
Hinweis: Diese Zusammenfassung soll als Einstieg in die Programmierung in C dienen. Es wird empfohlen, die Beispiele selbst auszuprobieren und weiterführende Literatur zu konsultieren, um ein tieferes Verständnis zu erlangen.
Viel Erfolg beim Programmieren!
×
MyUniNotes is a free, non-profit project to make education accessible for everyone.
If it has helped you, consider giving back! Even a small donation makes a difference.
These are my personal notes. While I strive for accuracy, I’m still a student myself. Thanks for being part of this journey!