Was ist der Grund dafür, dass Fread / Fwrite Größe und Argumente als Argumente verwendet?

96

Wir hatten hier bei der Arbeit eine Diskussion darüber, warum fread und fwrite eine Größe pro Mitglied annehmen und die Anzahl der gelesenen / geschriebenen Mitglieder zählen und zurückgeben, anstatt nur einen Puffer und eine Größe zu nehmen. Die einzige Verwendung, die wir finden könnten, ist, wenn Sie ein Array von Strukturen lesen / schreiben möchten, die nicht gleichmäßig durch die Plattformausrichtung teilbar sind und daher aufgefüllt wurden, aber dies kann nicht so häufig sein, dass diese Auswahl gerechtfertigt ist im Design.

Aus FREAD (3) :

Die Funktion fread () liest nmemb-Datenelemente mit einer Größe von jeweils Bytes aus dem Stream, auf den der Stream zeigt, und speichert sie an der von ptr angegebenen Stelle.

Die Funktion fwrite () schreibt nmemb-Datenelemente mit einer Größe von jeweils Byte in den Stream, auf den der Stream zeigt, und erhält sie von der durch ptr angegebenen Position.

fread () und fwrite () geben die Anzahl der erfolgreich gelesenen oder geschriebenen Elemente zurück (dh nicht die Anzahl der Zeichen). Wenn ein Fehler auftritt oder das Dateiende erreicht ist, ist der Rückgabewert eine kurze Anzahl von Elementen (oder Null).

David Holm
quelle
10
Hey, das ist eine gute Frage. Ich habe mich immer darüber gewundert
Johannes Schaub - litb
1
Bitte überprüfen Sie diesen Thread: stackoverflow.com/questions/8589425/how-does-fread-really-work
Franken

Antworten:

22

Es basiert darauf, wie Fread implementiert wird.

Die Single UNIX-Spezifikation besagt

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.

fgetc hat auch diesen Hinweis:

Da fgetc () mit Bytes arbeitet, kann das Lesen eines Zeichens, das aus mehreren Bytes besteht (oder "ein Mehrbytezeichen"), mehrere Aufrufe von fgetc () erfordern.

Dies geht natürlich auf ausgefallene Zeichencodierungen mit variablen Bytes wie UTF-8 zurück.

Die SUS stellt fest, dass dies tatsächlich den ISO C-Dokumenten entnommen ist.

Powerlord
quelle
72

Der Unterschied zwischen fread (buf, 1000, 1, stream) und fread (buf, 1, 1000, stream) besteht darin, dass Sie im ersten Fall nur einen Block von 1000 Bytes oder nuthin erhalten, wenn die Datei kleiner ist und in der Im zweiten Fall erhalten Sie alles in der Datei mit weniger als und bis zu 1000 Byte.

Peter Miehle
quelle
4
Obwohl das stimmt, erzählt das nur einen kleinen Teil der Geschichte. Es wäre besser, etwas zu kontrastieren, das beispielsweise ein Array von int-Werten oder ein Array von Strukturen liest.
Jonathan Leffler
3
Dies wäre eine gute Antwort, wenn die Begründung abgeschlossen wäre.
Matt Joiner
13

Dies ist reine Spekulation, aber in den Tagen (einige gibt es noch) waren viele Dateisysteme keine einfachen Byte-Streams auf einer Festplatte.

Viele Dateisysteme waren auf Datensätzen basierend. Um solche Dateisysteme auf effiziente Weise zu erfüllen, müssen Sie die Anzahl der Elemente ("Datensätze") angeben, damit fwrite / fread den Speicher als Datensätze und nicht nur als Byte-Streams bearbeiten kann.

nr
quelle
1
Ich bin froh, dass jemand das angesprochen hat. Ich habe viel mit Dateisystemspezifikationen und FTP gearbeitet und Datensätze / Seiten und andere Blockierungskonzepte werden sehr stark unterstützt, obwohl niemand mehr diese Teile der Spezifikationen verwendet.
Matt Joiner
9

Lassen Sie mich hier diese Funktionen korrigieren:

size_t fread_buf( void* ptr, size_t size, FILE* stream)
{
    return fread( ptr, 1, size, stream);
}


size_t fwrite_buf( void const* ptr, size_t size, FILE* stream)
{
    return fwrite( ptr, 1, size, stream);
}

Als Begründung für die Parameter zu fread()/ fwrite()habe ich meine Kopie von K & R vor langer Zeit verloren, sodass ich nur raten kann. Ich denke, dass eine wahrscheinliche Antwort darin besteht, dass Kernighan und Ritchie einfach gedacht haben, dass das Ausführen von binären E / A am natürlichsten für Arrays von Objekten erfolgen würde. Möglicherweise haben sie auch gedacht, dass Block-E / A auf einigen Architekturen schneller / einfacher zu implementieren sind oder was auch immer.

Auch wenn der C-Standard dies spezifiziert fread()und fwrite()in Bezug auf fgetc()und implementiert werden soll fputc(), denken Sie daran, dass der Standard lange nach der Definition von C durch K & R entstanden ist und dass die im Standard angegebenen Dinge möglicherweise nicht in den ursprünglichen Designerideen enthalten waren. Es ist sogar möglich, dass die Aussagen in K & Rs "The C Programming Language" nicht mit denen übereinstimmen, als die Sprache zum ersten Mal entworfen wurde.

Zum Schluss sagt PJ Plauger fread()in "The Standard C Library" Folgendes :

Wenn das size(zweite) Argument größer als eins ist, können Sie nicht feststellen, ob die Funktion auch size - 1zusätzliche Zeichen liest, die über das hinausgehen, was sie meldet. In der Regel ist es besser, die Funktion als fread(buf, 1, size * n, stream);statt aufzurufenfread(buf, size, n, stream);

Grundsätzlich sagt er, dass fread()die Benutzeroberfläche defekt ist. Denn fwrite()er merkt an, dass "Schreibfehler im Allgemeinen selten sind, daher ist dies kein großes Manko" - eine Aussage, der ich nicht zustimmen würde.

Michael Burr
quelle
17
Eigentlich mache ich es oft andersherum: fread(buf, size*n, 1, stream);Wenn unvollständige Lesevorgänge eine Fehlerbedingung sind, ist es einfacher fread, einfach 0 oder 1 zurückzugeben, als die Anzahl der gelesenen Bytes. Dann können Sie beispielsweise if (!fread(...))das Ergebnis mit der angeforderten Anzahl von Bytes vergleichen (was zusätzlichen C-Code und zusätzlichen Maschinencode erfordert).
R .. GitHub STOP HELPING ICE
1
@R .. Achten Sie nur darauf, diese Größe * count! = 0 zusätzlich zu! Fread (...) zu überprüfen. Wenn size * count == 0 ist, erhalten Sie bei einem erfolgreichen Lesevorgang (von null Bytes) einen Rückgabewert von Null, feof () und ferror () werden nicht gesetzt, und errno ist etwas Unsinniges wie ENOENT oder noch schlimmer , etwas Irreführendes (und möglicherweise kritisch Brechendes) wie EAGAIN - sehr verwirrend, zumal im Grunde keine Dokumentation dieses Gotcha auf dich schreit.
Pegasus Epsilon
3

Wahrscheinlich geht es auf die Art und Weise zurück, wie Datei-E / A implementiert wurde. (damals) Es war möglicherweise schneller, in Blöcken in Dateien zu schreiben / zu lesen, als alles auf einmal zu schreiben.

Dolch
quelle
Nicht wirklich. Die C - Spezifikation für fwrite stellt fest , dass es Anrufe fputc wiederholt macht: opengroup.org/onlinepubs/009695399/functions/fwrite.html
Powerlord
1

Bei einer Implementierung, bei der das Lesen von Teildatensätzen vermieden werden kann, kann es vorteilhaft sein, separate Argumente für Größe und Anzahl zu haben. Wenn man Einzelbyte-Lesevorgänge aus einer Pipe verwenden würde, selbst wenn man Daten mit festem Format verwenden würde, müsste man die Möglichkeit berücksichtigen, dass ein Datensatz auf zwei Lesevorgänge aufgeteilt wird. Wenn stattdessen z. B. ein nicht blockierender Lesevorgang von bis zu 40 Datensätzen mit jeweils 10 Bytes angefordert werden könnte, wenn 293 Bytes verfügbar sind, und das System 290 Bytes (29 ganze Datensätze) zurückgeben könnte, während 3 Bytes für den nächsten Lesevorgang bereit bleiben, wäre dies der Fall viel bequemer sein.

Ich weiß nicht, inwieweit Implementierungen von fread mit einer solchen Semantik umgehen können, aber sie könnten sicherlich bei Implementierungen nützlich sein, die versprechen, sie zu unterstützen.

Superkatze
quelle
@PegasusEpsilon: Wenn z. B. ein Programm dies tut fread(buffer, 10000, 2, stdin)und der Benutzer nach Eingabe von 18.000 Bytes newline-ctrl-D eingibt, wäre es schön, wenn die Funktion die ersten 10.000 Bytes zurückgeben könnte, während die verbleibenden 8.000 für zukünftige kleinere Leseanforderungen ausstehen, aber vorhanden sind Gibt es Implementierungen, bei denen dies passieren würde? Wo würden die 8.000 Bytes bis zu diesen zukünftigen Anforderungen gespeichert?
Supercat
Nachdem wir es gerade getestet haben, stellt sich heraus, dass fread () nicht so funktioniert, wie ich es in dieser Hinsicht für am bequemsten halte, aber das Zurückstecken von Bytes in den Lesepuffer nach dem Ermitteln eines kurzen Lesevorgangs ist wahrscheinlich etwas mehr als wir erwarten sollten Standardbibliotheksfunktionen sowieso. fread () liest Teildatensätze und schiebt sie in den Puffer, aber der Rückgabewert gibt an, wie viele vollständige Datensätze gelesen wurden, und sagt Ihnen nichts (was für mich ziemlich ärgerlich ist) über kurze Lesevorgänge, die von stdin abgezogen wurden.
Pegasus Epsilon
... Fortsetzung ... Das Beste, was Sie tun können, ist wahrscheinlich, Ihren Lesepuffer vor fread mit Nullen zu füllen und den Datensatz zu überprüfen, nachdem fread () angibt, dass er auf Nicht-Null-Bytes beendet ist. Hilft Ihnen nicht besonders, wenn Ihre Datensätze möglicherweise null enthalten, aber wenn Sie sizemehr als 1 verwenden, na ja ... Für den Datensatz gibt es möglicherweise auch Ioctls oder anderen Unsinn, den Sie auf den Stream anwenden können, um ihn zu erstellen benimm dich anders, ich habe mich nicht so tief damit beschäftigt.
Pegasus Epsilon
Außerdem habe ich meinen früheren Kommentar wegen Ungenauigkeit gelöscht. Naja.
Pegasus Epsilon
@PegasusEpsilon: C wird auf so vielen Plattformen verwendet, die unterschiedliche Verhaltensweisen berücksichtigen. Die Vorstellung, dass Programmierer erwarten sollten, dass sie bei allen Implementierungen dieselben Funktionen und Garantien verwenden, ignoriert das bisher beste Merkmal von C: Das Design würde es Programmierern ermöglichen, Funktionen und Garantien auf Plattformen zu verwenden, auf denen sie verfügbar waren. Einige Arten von Streams können Pushbacks beliebiger Größe problemlos unterstützen, und eine freadArbeit, wie Sie sie für solche Streams beschrieben haben, wäre nützlich, wenn es eine Möglichkeit gäbe, Streams zu identifizieren, die auf diese Weise funktionieren.
Supercat
0

Ich denke, das liegt daran, dass C keine Funktionsüberladung aufweist. Wenn es welche gäbe, wäre die Größe überflüssig. In C können Sie jedoch keine Größe eines Array-Elements bestimmen, sondern müssen eine angeben.

Bedenken Sie:

int intArray[10];
fwrite(intArray, sizeof(int), 10, fd);

Wenn fwrite die Anzahl der Bytes akzeptiert, können Sie Folgendes schreiben:

int intArray[10];
fwrite(intArray, sizeof(int)*10, fd);

Aber es ist einfach ineffizient. Sie haben eine Größe von (int) mal mehr Systemaufrufen.

Ein weiterer Punkt, der berücksichtigt werden sollte, ist, dass Sie normalerweise nicht möchten, dass ein Teil eines Array-Elements in eine Datei geschrieben wird. Sie wollen die ganze ganze Zahl oder nichts. fwrite gibt eine Reihe von Elementen zurück, die erfolgreich geschrieben wurden. Was würden Sie tun, wenn Sie feststellen, dass nur 2 niedrige Bytes eines Elements geschrieben sind?

Auf einigen Systemen (aufgrund der Ausrichtung) können Sie nicht auf ein Byte einer Ganzzahl zugreifen, ohne eine Kopie zu erstellen und zu verschieben.

Vanuan
quelle