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.
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.txtunddifferencesFoundInFile2.txtschreiben - 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
lseekoderfstat– aber denke daran, den Dateizeiger zurückzusetzen. - Speicherlecks: Jeder
mallocbraucht einfree. Nutze Valgrind. - Vergessen der Berechtigungen: Beim Erstellen der Ausgabedateien mit
openundO_CREATmusst 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!