16.20 Pufferung einstellen – »setbuf()« und »setvbuf()« 

Eine kurze Erklärung zur Pufferung: Die Standardeinstellung ist bei ANSI-C-Compilern die Vollpufferung. Dies ist auch sinnvoller und schneller als keine Pufferung, da weniger Lese- und Schreiboperationen etwa auf der Festplatte oder dem Arbeitsspeicher stattfinden. Die Puffergröße ist abhängig vom Compiler, liegt aber meistens bei 512 und 4096 Bytes. Die Größe ist in der Headerdatei <stdio.h> mit der Konstante BUFSIZ angegeben.
Bei einer Pufferung, die zeichenweise eingestellt ist, würde ein Kopiervorgang zum Beispiel so ablaufen:
-> Lese aus Datei ein Zeichen <- Schreibe in eine Datei ein Zeichen -> Lese aus Datei ein Zeichen <- Schreibe in eine Datei ein Zeichen -> Lese aus Datei ein Zeichen <- Schreibe in eine Datei ein Zeichen .... usw. Zeichen für Zeichen
Bei einer Datei mit 100 Bytes wären das 100 Zugriffe zum Lesen im Wechsel mit 100 Zugriffen zum Schreiben.
Bei Vollpufferung läuft dies so: Es wird so lange gelesen, bis der Puffer voll ist (BUFSIZE), und dann wird geschrieben. Im obigen Beispiel würde bei Vollpufferung einmal gelesen und einmal geschrieben.
Um hier selbst in die Pufferung eingreifen zu können, stehen Ihnen die Funktionen setbuf() oder setvbuf() zur Verfügung.
16.20.1 Die Funktion »setbuf()« 

Mit der Funktion setbuf() wird einer Datei ein Dateipuffer zugeordnet. Die Syntax lautet:
#include <stdio.h> void setbuf(FILE * restrict datei, char * restrict puffer);
Der geöffnete Stream datei erhält durch setbuf() den Puffer puffer. Die Größe des Puffers wird durch den Wert von BUFSIZ vorgegeben. Die symbolische Konstante BUFSIZ befindet sich in der Headerdatei <stdio.h>.
Wie groß BUFSIZ auf Ihrem System ist, ermitteln Sie mit folgendem Listing:
/* bufsize.c */ #include <stdio.h> #include <stdlib.h> int main(void) { printf("Die max. Groesse des Puffers: %d\n",BUFSIZ); return EXIT_SUCCESS; }
Der Wert für BUFSIZ dürfte in der Regel 256 KB, 512 KB oder auch 4096 KB betragen. Dies ist abhängig vom System und vom Compiler.
Geben Sie hingegen für puffer den NULL-Zeiger an, erfolgt die Datenübertragung ungepuffert. Das würde eine Übertragung Zeichen für Zeichen bedeuten und natürlich erheblich mehr Zeit beanspruchen, da jedes einzelne Zeichen gelesen und anschließend wieder geschrieben wird.
Sie müssen die Funktion setbuf() unmittelbar nach dem Öffnen einer Datei aufrufen – noch vor einer Lese- oder Schreiboperation.
Zur Demonstration folgt ein Listing, das gepuffertes und ungepuffertes Kopieren von Daten vergleicht. Außerdem werden dabei die Funktionen getc() und putc() zum Lesen und Schreiben verwendet, die zwar zeichenweise arbeiten, aber dennoch vom Puffer abhängig sind. Hier das Listing:
/* test_setbuffer.c */ #include <stdio.h> #include <stdlib.h> #include <time.h> #define DATEIGROESSE 10000000L #define DATEI1 "test.1" #define DATEI2 "test.2" void copy1(char *quelle, char *ziel) { FILE *q,*z; int c; time_t t1 = time(NULL); printf("Kopiere zeichenweise mit getc() und putc()\n"); q=fopen(quelle, "rb"); if( q != NULL) { z = fopen(ziel, "wb"); if(NULL == z) { fprintf(stderr,"Fehler beim Öffnen (%s)\n",ziel); exit(EXIT_FAILURE); } } else { fprintf(stderr, "Fehler beim Öffnen von %s\n", quelle); exit(EXIT_FAILURE); } while((c=getc(q)) != EOF) putc(c,z); fclose(q); fclose(z); printf("Zeit = %d sec.\n",time(NULL)-t1); } void copy2(char *quelle, char *ziel) { FILE *q,*z; static char puffer1[BUFSIZ]; static char puffer2[BUFSIZ]; int c; time_t t1 = time(NULL); printf("Gepuffertes Kopieren mit setbuf(stream,BUFSIZE)\n"); q=fopen(quelle,"rb"); if(q != NULL) { z = fopen(ziel, "wb"); if(NULL == z) { fprintf(stderr,"Fehler beim Öffnen (%s)\n",ziel); exit(EXIT_FAILURE); } } else { fprintf(stderr, "Fehler beim Öffnen von %s\n", quelle); exit(EXIT_FAILURE); } setbuf(q,puffer1); setbuf(z,puffer2); while((c=getc(q)) != EOF) putc(c,z); fclose(q); fclose(z); printf("Zeit = %d sec.\n",time(NULL)-t1); } void copy3(char *quelle, char *ziel) { FILE *q,*z; int c; time_t t1 = time(NULL); printf("Ungepuffertes Kopieren mit setbuf(stream, NULL)\n"); q = fopen(quelle, "rb"); if(q != NULL) { z = fopen(ziel, "wb"); if(NULL == z) { fprintf(stderr, "Fehler beim Öffnen (%s)\n", ziel); exit(EXIT_FAILURE); } } else { fprintf(stderr, "Fehler beim Öffnen von %s\n", quelle); exit(EXIT_FAILURE); } setbuf(q,NULL); setbuf(z,NULL); while((c=getc(q)) != EOF) putc(c,z); fclose(q); fclose(z); printf("Zeit = %d sec.\n",time(NULL)-t1); } void erzeuge_datei(void) { FILE *create = fopen(DATEI1, "wb"); if(NULL == create) { fprintf(stderr, "Konnte keine Datei erzeugen\n"); exit(EXIT_FAILURE); } fseek(create,DATEIGROESSE-1,SEEK_SET); putc('x',create); fclose(create); } int main(void) { printf("Datei %s wird erzeugt\n", DATEI1); erzeuge_datei(); copy1(DATEI1,DATEI2); copy2(DATEI1,DATEI2); copy3(DATEI1,DATEI2); remove(DATEI1); remove(DATEI2); return EXIT_SUCCESS; }
Abbildung 16.8 Zeitvergleiche mit gepufferter und ungepufferter Einstellung
Zuerst wird eine Datei von zehn Megabyte Größe mit der Funktion erzeuge_datei() angelegt. Anschließend wird die erzeugte Datei test.1 in die Datei test.2 kopiert, ohne die Funktion setbuf() zu verwenden (Funktion copy1()).
Als Nächstes wird die Funktion copy2() verwendet, bei der zum ersten Mal setbuf() eingesetzt wird. Als Pufferungsgröße wird hierbei die Konstante BUFSIZ verwendet. Der Zeitverbrauch ist wieder derselbe wie zuvor ohne setbuf(). Also können Sie sich setbuf() mit der Größe von BUFSIZ ersparen, da dies die Standardeinstellung für die Funktionen getc() und putc() zu sein scheint.
Als Letztes wurde die Funktion copy3() ausgeführt, bei der der Puffer auf NULL gesetzt wird. Somit wird ungepuffert kopiert. Das dauert natürlich eine Weile, da nach jedem Lesezugriff pro Byte gleich wieder ein Schreibzugriff erfolgt.
Am Ende werden diese beiden Dateien mittels remove() wieder gelöscht, damit nicht unnötig Datenmüll auf der Platte übrig bleibt.
Hinweis |
Die Geschwindigkeit des Kopiervorgangs – wie im Listing demonstriert – ist nicht nur von der Power des Rechners abhängig. Einen sehr bedeutenden Anteil daran hat auch der Compiler selbst. Ich habe obiges Programm testweise mit einem anderen Compiler übersetzt, und es lief bis zu dreimal schneller. |
Hinweis |
Die Funktion setbuf() ist mittlerweile veraltet und wird nur noch aus Kompatibilitätsgründen beibehalten. Es empfiehlt sich, die neuere Funktion setvbuf() zur Veränderung des Dateipuffers zu verwenden. |
16.20.2 Die Funktion »setvbuf()« 

Zur Puffereinstellung kann aber auch die Funktion setvbuf() eingesetzt werden, die ähnlich wie setbuf() funktioniert. Hierzu lautet die Syntax:
#include <stdio.h> int setvbuf(FILE * restrict datei,char * restrict puffer,int modus, size_t puffergroesse);
Wenn alles in Ordnung ging, liefert diese Funktion 0 zurück, andernfalls einen Wert ungleich 0. Die ersten beiden Parameter (FILE *datei,char *puffer) haben dieselbe Bedeutung wie schon bei der Funktion setbuf(). Zusätzlich stehen hier für den Parameter modus drei symbolische Konstanten zur Verfügung (siehe Tabelle 16.8).
Puffertyp (Modus) | Bedeutung |
_IOLBF |
Die Datei wird zeilenweise gepuffert. Hierbei wird bei einer Lese- und Schreiboperation der Puffer gefüllt und erst übertragen, wenn ein Newline-Zeichen im Puffer vorkommt oder der Puffer voll ist. |
_IONBF |
Die Ein-/Ausgabe wird gar nicht gepuffert. Die Daten werden direkt aus der Datei übertragen. Die Parameter puffer und puffergroesse haben hier keinerlei Effekt. |
_IOFBF |
Die Ein-/Ausgabe wird voll gepuffert. Der Puffer wird komplett gefüllt, bis die Daten übertragen werden. |
Falls hierbei für puffer NULL angegeben wird, alloziert die Funktion einen eigenen Speicher der Größe puffergrösse. Das hört sich komplexer an, als es ist. setbuf() ohne Pufferung verwenden Sie beispielsweise so:
setbuf(quelle,NULL);
Hiermit wurde für den Stream quelle die Pufferung abgeschaltet (ungepuffert). Mit setvbuf() würde dies so erreicht:
setvbuf(quelle, NULL, _IONBF, BUFSIZ);
Für den Stream quelle wurde der Puffer nun ebenso abgeschaltet.
Wenn Sie die Pufferung auf z. B. 50 KB einstellen wollen, um Daten vom Stream quelle zum Stream ziel zu kopieren, so ergeben sich bei setvbuf() folgende Argumente:
setvbuf(quelle, NULL, _IOFBF, 50000L); setvbuf(ziel, NULL, _IOFBF, 50000L);
Für eine zeilenweise Pufferung könnten Sie folgende Angaben machen:
setvbuf(quelle, NULL, _IOLBF, 80); setvbuf(ziel, NULL, _IOLBF, 80);
So werden von quelle nach ziel mindestens 80 Zeichen kopiert, oder es wird bis zum nächsten Newline-Zeichen (\n) kopiert.
Sie haben auf diese Weise mit der Funktion setvbuf() die Möglichkeit, einen Dateipuffer bestimmter Länge zuzuordnen.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.