Assignment Chef icon Assignment Chef
All German tutorials

Programming lesson

Dateivergleich mit System Calls in C: Schritt-für-Schritt-Tutorial für CSCI 1730

Lerne, wie du mit open, close, read und write zwei Dateien byteweise vergleichst, Unterschiede in Ausgabedateien schreibst und die Ausführungszeit misst – genau wie im CSCI 1730 Projekt 3.

Dateivergleich C System Calls C open close read write CSCI 1730 Projekt 3 dynamische Speicherverwaltung C byteweiser Vergleich gettimeofday C Linux Systemprogrammierung C Programmierung Tutorial Datei-I/O System Calls Speicherlecks vermeiden C Valgrind C C Projekt 3 Lösung Unterschiede in Dateien finden C C Programmierung für Anfänger System Calls lernen

Einleitung: Warum Dateivergleich mit System Calls?

In der Systemprogrammierung unter Linux ist der direkte Umgang mit Dateien über System Calls wie open, close, read und write essenziell. Dieses Tutorial führt dich durch die Implementierung eines Programms, das zwei Dateien byteweise vergleicht und die Unterschiede in separate Ausgabedateien schreibt – genau wie im CSCI 1730 Projekt 3. Dabei lernst du nicht nur die Datei-I/O-System Calls, sondern auch dynamische Speicherverwaltung mit malloc und free sowie Zeitmessung mit gettimeofday.

Stell dir vor, du entwickelst ein Tool, das Änderungen in Konfigurationsdateien erkennt – ähnlich wie diff unter Linux, aber mit eigenen Händen programmiert. Dieses Wissen ist die Grundlage für viele moderne Anwendungen, von Versionskontrollsystemen bis hin zu Datenabgleich in der Cloud. Aktuell, im Mai 2026, sind Effizienz und Speicherverwaltung wichtiger denn je, besonders bei der Verarbeitung großer Logdateien in KI-Trainingspipelines.

Projektanforderungen im Überblick

Dein Programm muss:

  • Zwei Eingabedateien (über Kommandozeilenargumente) öffnen und lesen
  • Byteweise vergleichen und Unterschiede in differencesFoundInFile1.txt und differencesFoundInFile2.txt schreiben
  • Zwei Schritte implementieren: Step 1 (byteweises Lesen mit 2-Byte-Puffer) und Step 2 (komplettes Einlesen in dynamische Arrays)
  • Zeitmessung für jeden Schritt mit gettimeofday
  • Nur die erlaubten Header verwenden: stdio.h, stdlib.h, unistd.h, fcntl.h, sys/stat.h, sys/time.h, string.h
  • Fehlerbehandlung bei Dateioperationen und Speicherfehlern

Schritt 1: Byteweiser Vergleich mit kleinem Puffer

In Step 1 liest du jeweils ein Byte aus beiden Dateien, vergleichst sie und schreibst unterschiedliche Bytes sofort in die Ausgabedatei. Der Puffer ist nur 2 Bytes groß (ein Byte Daten, ein Byte Nullterminator für write). Dies simuliert eine ressourcenschonende Methode, die wenig Speicher benötigt – ideal für eingebettete Systeme oder wenn der Speicher begrenzt ist.

void step1(int fd1, int fd2, int fd_out1) {
    char buf1[2] = {0};
    char buf2[2] = {0};
    ssize_t r1, r2;
    while ((r1 = read(fd1, buf1, 1)) > 0 && (r2 = read(fd2, buf2, 1)) > 0) {
        if (buf1[0] != buf2[0]) {
            write(fd_out1, buf1, 1);
        }
    }
    // Restliche Bytes behandeln...
}

Beachte: Bei unterschiedlich langen Dateien müssen die restlichen Bytes der längeren Datei ebenfalls als Unterschied gewertet werden. Das ist ein häufiger Fehler – achte darauf!

Schritt 2: Vergleich mit dynamischen Arrays

In Step 2 liest du beide Dateien komplett in dynamisch allozierte Arrays ein. Dazu musst du die Dateigröße ermitteln (z.B. mit stat oder durch Lesen bis zum Ende). Dann allozierst du exakt so viel Speicher, wie benötigt wird. Nach dem Vergleich schreibst du die Unterschiede aus Datei 2 in ein drittes Array und dann in die Ausgabedatei. Diese Methode ist speicherintensiver, aber schneller bei vielen Zugriffen, da die Daten im RAM liegen.

void step2(int fd1, int fd2, int fd_out2) {
    struct stat st1, st2;
    fstat(fd1, &st1);
    fstat(fd2, &st2);
    char *buf1 = malloc(st1.st_size);
    char *buf2 = malloc(st2.st_size);
    read(fd1, buf1, st1.st_size);
    read(fd2, buf2, st2.st_size);
    // Vergleiche und schreibe Unterschiede...
    free(buf1);
    free(buf2);
}

Vergiss nicht, nach der Nutzung den Speicher freizugeben! Ein Valgrind-Check ist Pflicht.

Zeitmessung mit gettimeofday

Um die Performance der beiden Schritte zu vergleichen, nutzt du gettimeofday vor und nach jedem Schritt. Die Differenz in Mikrosekunden gibt dir Aufschluss darüber, welcher Ansatz schneller ist. Bei großen Dateien (mehrere MB) wirst du sehen, dass Step 2 meist schneller ist, da weniger System Calls nötig sind.

struct timeval start, end;
gettimeofday(&start, NULL);
step1(fd1, fd2, fd_out1);
gettimeofday(&end, NULL);
long elapsed = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec);
printf("Step 1 took %ld microseconds\n", elapsed);

Häufige Fehler und Tipps

  • Fehlerhafte Dateigrößenermittlung: Verwende lseek oder fstat – aber denke daran, den Dateizeiger zurückzusetzen.
  • Speicherlecks: Jeder malloc braucht ein free. Nutze Valgrind.
  • Vergessen der Berechtigungen: Beim Erstellen der Ausgabedateien mit open und O_CREAT musst du die Modi angeben, z.B. S_IRUSR | S_IWUSR.
  • Pufferüberläufe: Achte darauf, dass du nicht über das Ende des allozierten Speichers schreibst.

Fazit

Dieses Projekt gibt dir tiefe Einblicke in die Systemprogrammierung unter Linux. Du lernst, wie Betriebssysteme Dateizugriffe handhaben und wie wichtig effiziente Speicherverwaltung ist. Die erlernten Konzepte – System Calls, dynamischer Speicher, Zeitmessung – sind die Bausteine für viele fortgeschrittene Anwendungen, sei es in der Datenverarbeitung, in Embedded Systems oder in der Entwicklung von Datenbanken. Viel Erfolg bei der Implementierung!