Quelle est la raison pour laquelle fread / fwrite prend la taille et compte comme arguments?

96

Nous avons eu une discussion ici au travail pour savoir pourquoi fread et fwrite prennent une taille par membre et comptent et renvoient le nombre de membres lus / écrits plutôt que de simplement prendre un tampon et une taille. La seule utilisation que nous pourrions trouver est si vous voulez lire / écrire un tableau de structures qui ne sont pas uniformément divisibles par l'alignement de la plate-forme et qui ont donc été remplies mais qui ne peuvent pas être si courantes pour justifier ce choix dans la conception.

De FREAD (3) :

La fonction fread () lit nmemb éléments de données, chaque taille octets de long, à partir du flux pointé par stream, les stockant à l'emplacement donné par ptr.

La fonction fwrite () écrit nmemb éléments de données, chaque taille octets de long, dans le flux pointé par stream, en les obtenant à partir de l'emplacement donné par ptr.

fread () et fwrite () renvoient le nombre d'éléments lus ou écrits avec succès (c'est-à-dire pas le nombre de caractères). Si une erreur se produit ou si la fin de fichier est atteinte, la valeur de retour est un nombre d'éléments courts (ou zéro).

David Holm
la source
10
hé c'est une bonne question. Je me suis toujours demandé à ce sujet
Johannes Schaub - litb

Réponses:

22

Il est basé sur la façon dont fread est implémenté.

La spécification UNIX unique dit

Pour chaque objet, des appels de taille doivent être faits à la fonction fgetc () et les résultats stockés, dans l'ordre de lecture, dans un tableau de caractères non signés recouvrant exactement l'objet.

fgetc a également cette note:

Puisque fgetc () fonctionne sur des octets, la lecture d'un caractère composé de plusieurs octets (ou «un caractère multi-octets») peut nécessiter plusieurs appels à fgetc ().

Bien sûr, cela précède les encodages de caractères fantaisie à octets variables comme UTF-8.

Le SUS note que cela est en fait tiré des documents ISO C.

Powerlord
la source
72

La différence entre fread (buf, 1000, 1, stream) et fread (buf, 1, 1000, stream) est que dans le premier cas vous n'obtenez qu'un seul morceau de 1000 octets ou nuthin, si le fichier est plus petit et dans le deuxième cas, vous obtenez tout dans le fichier moins de et jusqu'à 1000 octets.

Peter Miehle
la source
4
Bien que vrai, cela ne raconte qu'une petite partie de l'histoire. Il serait préférable de comparer quelque chose qui lit, par exemple, un tableau de valeurs int, ou un tableau de structures.
Jonathan Leffler
3
Cela ferait une excellente réponse si la justification était complétée.
Matt Joiner
13

Ce sont de pures spéculations, mais à l'époque (certains sont encore là), de nombreux systèmes de fichiers n'étaient pas de simples flux d'octets sur un disque dur.

De nombreux systèmes de fichiers étaient basés sur des enregistrements, donc pour satisfaire ces systèmes de fichiers de manière efficace, vous devrez spécifier le nombre d'éléments («enregistrements»), permettant à fwrite / fread d'opérer sur le stockage en tant qu'enregistrements, pas seulement en tant que flux d'octets.

nos
la source
1
Je suis content que quelqu'un en ait parlé. J'ai beaucoup travaillé avec les spécifications de système de fichiers et FTP et les enregistrements / pages et autres concepts de blocage sont très fermement pris en charge, bien que personne n'utilise plus ces parties des spécifications.
Matt Joiner
9

Ici, permettez-moi de corriger ces fonctions:

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);
}

Quant à la justification des paramètres de fread()/ fwrite(), j'ai perdu ma copie de K&R il y a longtemps, donc je ne peux que deviner. Je pense qu'une réponse probable est que Kernighan et Ritchie ont peut-être simplement pensé que l'exécution d'E / S binaires se ferait le plus naturellement sur des tableaux d'objets. En outre, ils ont peut-être pensé que les E / S par blocs seraient plus rapides / plus faciles à implémenter ou autre chose sur certaines architectures.

Même si la norme C spécifie que fread()et doit fwrite()être mise en œuvre en termes de fgetc()et fputc(), rappelez-vous que la norme est apparue longtemps après que C a été définie par K&R et que les éléments spécifiés dans la norme n'étaient peut-être pas dans les idées originales des concepteurs. Il est même possible que les choses dites dans "The C Programming Language" de K&R ne soient pas les mêmes que lorsque le langage a été conçu pour la première fois.

Enfin, voici ce que PJ Plauger a à dire fread()dans "The Standard C Library":

Si le size(deuxième) argument est supérieur à un, vous ne pouvez pas déterminer si la fonction lit également jusqu'à size - 1caractères supplémentaires au-delà de ce qu'elle signale. En règle générale, il vaut mieux appeler la fonction au fread(buf, 1, size * n, stream);lieu de fread(buf, size, n, stream);

Fondamentalement, il dit que fread()l'interface est cassée. Car fwrite()il note que «les erreurs d'écriture sont généralement rares, donc ce n'est pas une lacune majeure» - une affirmation avec laquelle je ne suis pas d'accord.

Michael Burr
la source
17
En fait, j'aime souvent le faire dans l'autre sens: fread(buf, size*n, 1, stream);si des lectures incomplètes sont une condition d'erreur, il est plus simple de faire en sorte de freadrenvoyer simplement 0 ou 1 plutôt que le nombre d'octets lus. Ensuite, vous pouvez faire des choses comme if (!fread(...))au lieu d'avoir à comparer le résultat avec le nombre d'octets demandé (ce qui nécessite un code C supplémentaire et un code machine supplémentaire).
R .. GitHub STOP HELPING ICE
1
@R .. Assurez-vous simplement de vérifier cette taille * count! = 0 en plus de! Fread (...). Si size * count == 0, vous obtenez une valeur de retour nulle sur une lecture réussie (de zéro octet), feof () et ferror () ne seront pas définis, et errno sera quelque chose de non-sens comme ENOENT, ou pire , quelque chose de trompeur (et peut-être de manière critique) comme EAGAIN - très déroutant, d'autant plus qu'aucune documentation ne vous crie cela.
Pegasus Epsilon
3

Cela revient probablement à la manière dont les E / S de fichier ont été implémentées. (à l'époque) Il aurait peut-être été plus rapide d'écrire / lire des fichiers dans des blocs que de tout écrire en même temps.

dolch
la source
Pas vraiment. La spécification C pour fwrite note qu'elle fait des appels répétés à fputc: opengroup.org/onlinepubs/009695399/functions/fwrite.html
Powerlord
1

Avoir des arguments séparés pour la taille et le nombre pourrait être avantageux sur une implémentation qui peut éviter de lire des enregistrements partiels. Si l'on utilisait des lectures à un octet à partir de quelque chose comme un tube, même si l'on utilisait des données de format fixe, il faudrait prévoir la possibilité qu'un enregistrement soit divisé en deux lectures. Si pourrait à la place demander, par exemple, une lecture non bloquante de jusqu'à 40 enregistrements de 10 octets chacun lorsqu'il y a 293 octets disponibles, et demander au système de renvoyer 290 octets (29 enregistrements entiers) tout en laissant 3 octets prêts pour la prochaine lecture, ce serait être beaucoup plus pratique.

Je ne sais pas dans quelle mesure les implémentations de fread peuvent gérer une telle sémantique, mais elles pourraient certainement être utiles sur des implémentations qui pourraient promettre de les supporter.

supercat
la source
@PegasusEpsilon: Si par exemple un programme le fait fread(buffer, 10000, 2, stdin)et que l'utilisateur tape newline-ctrl-D après avoir tapé 18 000 octets, ce serait bien si la fonction pouvait renvoyer les 10 000 premiers octets tout en laissant les 8 000 restants en attente pour de futures demandes de lecture plus petites, mais y en a-t-il des implémentations où cela se produirait? Où les 8 000 octets seraient-ils stockés en attendant ces futures demandes?
supercat
Après l'avoir testé, il s'avère que fread () ne fonctionne pas de ce que je considérerais comme le plus pratique à cet égard, mais ensuite, le remplissage d'octets dans le tampon de lecture après avoir déterminé une lecture courte est probablement un peu plus que ce à quoi nous devrions nous attendre. la bibliothèque standard fonctionne de toute façon. fread () lira les enregistrements partiels et les poussera dans la mémoire tampon, mais la valeur de retour précisera combien d' enregistrements complets ont été lus et ne vous dit rien (ce qui est assez ennuyeux pour moi) sur les lectures courtes retirées de stdin.
Pegasus Epsilon
... suite ... Le mieux que vous puissiez faire est probablement de remplir votre tampon de lecture avec des valeurs nulles avant fread, et de vérifier l'enregistrement après où fread () dit qu'il a terminé pour tous les octets non nuls. Cela ne vous aide pas particulièrement lorsque vos enregistrements peuvent contenir null, mais si vous allez utiliser sizeplus de 1, eh bien ... Pour mémoire, il peut également y avoir des ioctls ou d'autres non-sens que vous pouvez appliquer au flux pour le rendre se comporter différemment, je n'ai pas approfondi cela.
Pegasus Epsilon
J'ai également supprimé mon commentaire précédent en raison d'inexactitudes. Tant pis.
Pegasus Epsilon
@PegasusEpsilon: C est utilisé sur tant de plates-formes, qui acceptent différents comportements. L'idée que les programmeurs devraient s'attendre à utiliser les mêmes fonctionnalités et garanties sur toutes les implémentations ignore ce qui avait été la meilleure fonctionnalité de C: que sa conception permettrait aux programmeurs d'utiliser des fonctionnalités et des garanties sur les plates-formes où elles étaient disponibles. Certains types de flux peuvent facilement prendre en charge des refoulements de taille arbitraire, et un freadtravail tel que vous l'avez décrit sur de tels flux serait utile s'il y avait un moyen d'identifier les flux qui fonctionnent de cette manière.
supercat
0

Je pense que c'est parce que C n'a pas de surcharge de fonctions. S'il y en avait, la taille serait redondante. Mais en C, vous ne pouvez pas déterminer la taille d'un élément de tableau, vous devez en spécifier une.

Considère ceci:

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

Si fwrite accepte le nombre d'octets, vous pouvez écrire ce qui suit:

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

Mais c'est tout simplement inefficace. Vous aurez sizeof (int) fois plus d'appels système.

Un autre point à prendre en compte est que vous ne voulez généralement pas qu'une partie d'un élément de tableau soit écrite dans un fichier. Vous voulez le nombre entier ou rien. fwrite renvoie un certain nombre d'éléments écrits avec succès. Donc, si vous découvrez que seuls 2 octets de poids faible d'un élément sont écrits, que feriez-vous?

Sur certains systèmes (en raison de l'alignement), vous ne pouvez pas accéder à un octet d'un entier sans créer une copie et un décalage.

Vanuan
la source