Wie funktioniert Fread wirklich?


75

Die Erklärung freadlautet wie folgt:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

Die Frage ist: Gibt es einen Unterschied in der Leseleistung zweier solcher Aufrufe an fread:

char a[1000];
  1. fread(a, 1, 1000, stdin);
  2. fread(a, 1000, 1, stdin);

Wird es jedes Mal sofort1000 Bytes lesen ?

Antworten:


106

Es kann einen Leistungsunterschied geben oder auch nicht. Es gibt einen Unterschied in der Semantik.

fread(a, 1, 1000, stdin);

versucht, 1000 Datenelemente zu lesen, von denen jedes 1 Byte lang ist.

fread(a, 1000, 1, stdin);

versucht 1 Datenelement zu lesen, das 1000 Bytes lang ist.

Sie sind anders, weil fread() die Anzahl der Datenelemente zurückgeben, die gelesen werden konnten, nicht die Anzahl der Bytes. Wenn es vor dem Lesen der vollen 1000 Bytes das Dateiende (oder eine Fehlerbedingung) erreicht, muss die erste Version genau angeben, wie viele Bytes es gelesen hat. Die zweite schlägt einfach fehl und gibt 0 zurück.

In der Praxis wird wahrscheinlich nur eine untergeordnete Funktion aufgerufen, die versucht, 1000 Bytes zu lesen, und angibt, wie viele Bytes tatsächlich gelesen werden. Bei größeren Lesevorgängen werden möglicherweise mehrere Anrufe auf niedrigerer Ebene getätigt. Die Berechnung des von zurückzugebenden Wertesfread() ist unterschiedlich, aber der Aufwand für die Berechnung ist trivial.

Es kann einen Unterschied geben, wenn die Implementierung vor dem Versuch, die Daten zu lesen, feststellen kann, dass nicht genügend Daten zum Lesen vorhanden sind. Wenn Sie beispielsweise aus einer 900-Byte-Datei lesen, liest die erste Version alle 900 Byte und gibt 900 zurück, während die zweite möglicherweise nicht die Mühe macht, etwas zu lesen. In beiden Fällen wird die Dateipositionsanzeige um die Anzahl der Zeichen vorgerückt erfolgreich gelesenen , dh 900, .

Aber im Allgemeinen sollten Sie wahrscheinlich auswählen, wie Sie es aufrufen möchten, basierend auf den Informationen, die Sie daraus benötigen. Lesen Sie ein einzelnes Datenelement, wenn ein teilweises Lesen nicht besser ist, als überhaupt nichts zu lesen. Lesen Sie kleinere Blöcke ein, wenn Teillesungen hilfreich sind.


der zweite könnte sich nicht die Mühe machen, etwas zu lesen. In beiden Fällen wird die Dateipositionsanzeige um die Anzahl der erfolgreich gelesenen Zeichen vorgerückt, dh 900, sollte es nicht sein, dass in der zweiten Version die Dateipositionsanzeige nicht vorrücken würde , da nichts gelesen wurde? Mit anderen Worten, sollte fread(a, 1000, N, stdin);der fp-Indikator nicht immer um ein Vielfaches von 1000?
Shahbaz

1
Vergiss es. In C11 unter 7.21.8.1.2 und 7.21.8.2.2 heißt es: Wenn ein Fehler auftritt, ist der resultierende Wert des
Shahbaz

Gibt es also keine Möglichkeit, die Position des Indikators wiederherzustellen? Oder um zu vermeiden, dass Sie den letzten Fehler lesen, der mit der Positionsanzeige zu tun hat?
David 天宇 Wong

1
@ David 天宇 Wong: Wenn Sie die Position wiederherstellen müssen, rufen Sie ftellvor freadund fseeknach dem Anruf an .
Keith Thompson

2
Die POSIX-Spezifikation ist viel strenger ... es erfordert, dass fread die Größe von fgetc pro Objekt festlegt, sodass in beiden Fällen genau die gleiche Anzahl von fgetc durchgeführt wird (die Rückgabewerte sind jedoch unterschiedlich).
Jim Balter

17

Gemäß der Spezifikation können die beiden durch die Implementierung unterschiedlich behandelt werden.

Wenn Ihre Datei weniger als 1000 Bytes enthält fread(a, 1, 1000, stdin)(lesen Sie 1000 Elemente zu je 1 Byte), werden weiterhin alle Bytes bis EOF kopiert. Andererseits wird das Ergebnis von fread(a, 1000, 1, stdin)(1 1000-Byte-Element lesen) in gespeicherta ist, nicht spezifiziert, da nicht genügend Daten vorhanden sind, um das Lesen des 'ersten' (und einzigen) 1000-Byte-Elements zu beenden.

Natürlich können einige Implementierungen das 'partielle' Element immer noch in so viele Bytes wie nötig kopieren.


15

Das wäre ein Implementierungsdetail. In glibc sind die beiden in der Leistung identisch, da es im Wesentlichen wie folgt implementiert ist (Ref http://sourceware.org/git/?p=glibc.git;a=blob;f=libio/iofread.c ):

size_t fread (void* buf, size_t size, size_t count, FILE* f)
{
    size_t bytes_requested = size * count;
    size_t bytes_read = read(f->fd, buf, bytes_requested);
    return bytes_read / size;
}

Beachten Sie, dass die C. und POSIXStandard garantiert nicht size, dass jedes Mal ein vollständiges Objekt der Größe gelesen werden muss. Wenn ein vollständiges Objekt nicht gelesen werden kann (z. B. stdinnur 999 Byte, aber Sie haben es angefordert size == 1000), bleibt die Datei in einem unbestimmten Zustand (C99 §7.19.8.1 / 2).

Bearbeiten: Siehe die anderen Antworten zu POSIX.


Sie erwähnen den POSIX-Standard, aber es erfordert die Implementierung von fread in Bezug auf fgetc, was viel deterministischer ist als die C-Anforderung.
Jim Balter

1
Super Antwort .. !! genau das, was jeder, der hier landet, braucht .. !!! Ich bin überrascht, dass es so viele Les Votes gibt ..
mk ..

Ist es das gleiche auch für fwrite?
mk ..

Wichtiger Punkt: Sie können die Datei beschädigen, wenn Sie Datensätze mit einer Größe von> 1 lesen.
ArekBulski

5

freadAnrufe getcintern. in MinixAnzahl , wie oft getcgenannt wird , ist einfach size*nmembso , wie oft getchängt von der aufgerufen wird Produkt dieser beiden. Also beides fread(a, 1, 1000, stdin)und fread(a, 1000, 1, stdin)wird getc 1000=(1000*1)mal laufen . Hier ist die einfache Implementierung freadvon Minix

size_t fread(void *ptr, size_t size, size_t nmemb, register FILE *stream){
register char *cp = ptr;
register int c;
size_t ndone = 0;
register size_t s;

if (size)
    while ( ndone < nmemb ) {
    s = size;
    do {
        if ((c = getc(stream)) != EOF)
            *cp++ = c;
        else
            return ndone;
    } while (--s);
    ndone++;
}

return ndone;
}

1
echte Antwort meiner Meinung nach
Sathvik

3

Möglicherweise gibt es keinen Leistungsunterschied, aber diese Anrufe sind nicht gleich.

  • fread Gibt die Anzahl der gelesenen Elemente zurück, sodass diese Aufrufe unterschiedliche Werte zurückgeben.
  • Wenn ein Element nicht vollständig gelesen werden kann, ist sein Wert unbestimmt:

Wenn ein Fehler auftritt, ist der resultierende Wert des Dateipositionsindikators für den Stream unbestimmt. Wenn ein Teilelement gelesen wird, ist sein Wert unbestimmt. (ISO / IEC 9899: TC2 7.19.8.1)

Es gibt keinen großen Unterschied in der glibc-Implementierung , bei der nur die Elementgröße mit der Anzahl der Elemente multipliziert wird, um zu bestimmen, wie viele Bytes gelesen werden sollen, und die gelesene Menge durch die Elementgröße am Ende dividiert wird. Die Version mit einer Elementgröße von 1 gibt jedoch immer die richtige Anzahl gelesener Bytes an. Wenn Sie jedoch nur Elemente einer bestimmten Größe vollständig lesen möchten, können Sie mit dem anderen Formular keine Unterteilung vornehmen.


1

Ein weiterer Satz aus http://pubs.opengroup.org/onlinepubs/000095399/functions/fread.html ist bemerkenswert

Die Funktion fread () liest in das Array, auf das ptr zeigt, bis zu nitems-Elementen, deren Größe durch die Größe in Bytes angegeben wird, aus dem Stream, auf den der Stream zeigt. Für jedes Objekt müssen Größenaufrufe an die Funktion fgetc () durchgeführt und die Ergebnisse in der gelesenen Reihenfolge in einem Array von vorzeichenlosen Zeichen gespeichert werden , die genau über dem Objekt liegen.

In beiden Fällen wird in Kürze auf die Daten von fgetc () zugegriffen ...!


Ja, ich fühle mich auch so, aber auf dieser Seite steht "Die auf dieser Referenzseite beschriebene Funktionalität entspricht dem ISO C-Standard." scheint zweifelhaft?
Jeegar Patel

@ Mr.32: Der Standard sagt dasselbe über Anrufe an fgetc, also ist Posix tatsächlich auf C99 ausgerichtet. Der Standard gibt einem konformen Programm jedoch keine Möglichkeit zu bestimmen, ob fgetc"wirklich" aufgerufen wird oder ob freadetwas anderes gleichwertig ist. 5.1.2.3 erklärt, dass der Standard nur das Verhalten einer "abstrakten Maschine" beschreibt und auflistet, auf welche Weise das tatsächliche Programm diesem Verhalten entsprechen muss. Dies wird in C ++ als "Als-ob" -Regel bezeichnet, aber nicht in C (mein Fehler früher). Nicht beobachtbares Verhalten muss nicht identisch sein.
Steve Jessop

Selbst wenn eine bestimmte Implementierung Ihnen die Möglichkeit gibt, zu zählen, wie oft fgetcaufgerufen wird (z. B. indem Sie Ihr Programm mit Ihrer eigenen Version dieser Funktion verknüpfen, z. B. indem Sie libc ändern und neu kompilieren), kann dies mit der Einschränkung geschehen dass die Funktion, die Sie ersetzen, nicht immer und nur aufgerufen wird, wenn der Standard die abstrakte Maschine als aufrufend beschreibt.
Steve Jessop

@SteveJessop "Nicht beobachtbares Verhalten muss nicht identisch sein." Warum ist es in POSIX dokumentiert?
Roman Byshko

@Beginner: weil eine Beschreibung des Verhaltens der abstrakten Maschine eine bequeme Möglichkeit ist, die Wirkung von fread(oder einem anderen Bit C-Code) zu beschreiben. Es ist in Posix so dokumentiert, einfach weil es im Standard so dokumentiert ist.
Steve Jessop

1

Ich wollte die Antworten hier klären. fread führt gepufferte E / A durch. Die tatsächlichen Leseblockgrößen, die Fread verwendet, werden durch die verwendete C-Implementierung bestimmt.

Alle modernen C-Bibliotheken haben mit den beiden Aufrufen die gleiche Leistung:

fread(a, 1, 1000, file);
fread(a, 1000, 1, file);

Sogar so etwas wie:

for (int i=0; i<1000; i++)
  a[i] = fgetc(file)

Sollte zu denselben Festplattenzugriffsmustern führen, obwohl fgetc aufgrund von mehr Aufrufen in den Standard-c-Bibliotheken und in einigen Fällen der Notwendigkeit, dass eine Festplatte zusätzliche Suchvorgänge ausführt, die sonst wegoptimiert worden wären, langsamer wäre.

Zurück zum Unterschied zwischen den beiden Formen von Fread. Ersteres gibt die tatsächliche Anzahl der gelesenen Bytes zurück. Letzterer gibt 0 zurück, wenn die Dateigröße kleiner als 1000 ist, andernfalls gibt er 1 zurück. In beiden Fällen würde der Puffer mit denselben Daten gefüllt, dh dem Inhalt der Datei bis zu 1000 Byte.

Im Allgemeinen möchten Sie wahrscheinlich den 2. Parameter (Größe) auf 1 setzen, damit Sie die Anzahl der gelesenen Bytes erhalten.


"Alle modernen C-Bibliotheken haben mit den beiden Aufrufen die gleiche Leistung" - ja. "In einigen Fällen muss eine Festplatte zusätzliche Suchvorgänge ausführen, die sonst wegoptimiert worden wären" - nein. fgetc liest einfach aus dem speicherinternen Puffer von stdio. Und selbst wenn der Stream als ungepuffert eingestellt wurde, puffert das zugrunde liegende Betriebssystem die Festplattenlesevorgänge.
Jim Balter

@ Jim: fgetc liest von stdio anders als fread. Das offensichtliche Ergebnis davon ist, dass fgetc immer die Anzahl der Suchvorgänge / Systemaufrufe (schlecht) maximiert, wobei fread die Anzahl der Suchvorgänge / Systemaufrufe minimiert, wenn Sie libc mehr Informationen über Ihre Aktivitäten bereitstellen.
Clarus

1
Entschuldigung, aber Sie haben keine Ahnung, wovon Sie sprechen ... es gibt keine Möglichkeit, wie sich Fread oder Fgetc unterscheiden, die sich auf die Anzahl der Suchvorgänge auswirken, und Sie haben diese absurde Behauptung nicht unterstützt. Beachten Sie, dass die Definition von fread in den C99- und POSIX-Standards in fgetc angegeben ist, wie an anderer Stelle auf dieser Seite erläutert.
Jim Balter
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.