Qual è il motivo per cui fread / fwrite prende la dimensione e conta come argomenti?


96

Abbiamo discusso qui al lavoro sul motivo per cui fread e fwrite prendono una dimensione per membro e contano e restituiscono il numero di membri letti / scritti piuttosto che prendere solo un buffer e una dimensione. L'unico uso che potremmo inventare è se vuoi leggere / scrivere un array di strutture che non sono uniformemente divisibili dall'allineamento della piattaforma e quindi sono state riempite ma non può essere così comune da giustificare questa scelta nel design.

Da FREAD (3) :

La funzione fread () legge nmemb elementi di dati, ogni dimensione byte, dallo stream puntato da stream, memorizzandoli nella posizione data da ptr.

La funzione fwrite () scrive elementi di dati nmemb, di ogni dimensione byte, nello stream puntato da stream, ottenendoli dalla posizione data da ptr.

fread () e fwrite () restituiscono il numero di elementi letti o scritti con successo (cioè, non il numero di caratteri). Se si verifica un errore o viene raggiunta la fine del file, il valore restituito è un conteggio di elementi brevi (o zero).


10
hey questa è una buona domanda.
Me

Risposte:


22

Si basa su come viene implementato fread .

La specifica UNIX singola dice

Per ogni oggetto, le chiamate di dimensione devono essere fatte alla funzione fgetc () ei risultati vengono memorizzati, nell'ordine letto, in un array di caratteri senza segno esattamente sovrapposti all'oggetto.

fgetc ha anche questa nota:

Poiché fgetc () opera sui byte, la lettura di un carattere costituito da più byte (o "un carattere multibyte") può richiedere più chiamate a fgetc ().

Naturalmente, questo precede le fantasiose codifiche di caratteri a byte variabile come UTF-8.

Il SUS osserva che questo è effettivamente preso dai documenti ISO C.


72

La differenza tra fread (buf, 1000, 1, stream) e fread (buf, 1, 1000, stream) è che nel primo caso si ottiene solo un blocco di 1000 byte o nuthin, se il file è più piccolo e nel secondo caso ottieni tutto nel file inferiore e fino a 1000 byte.


4
Sebbene sia vero, questo racconta solo una piccola parte della storia. Sarebbe meglio contrastare qualcosa che legge, diciamo, un array di valori int o un array di strutture.
Jonathan Leffler

3
Sarebbe un'ottima risposta se la giustificazione fosse stata completata.
Matt Joiner

13

Questa è pura speculazione, tuttavia ai tempi (alcuni sono ancora in circolazione) molti filesystem non erano semplici flussi di byte su un disco rigido.

Molti file system erano basati su record, quindi per soddisfare tali file system in modo efficiente, dovrai specificare il numero di elementi ("record"), consentendo a fwrite / fread di operare sulla memoria come record, non solo come flussi di byte.


1
Sono contento che qualcuno ne abbia parlato. Ho lavorato molto con le specifiche del filesystem e l'FTP e i record / pagine e altri concetti di blocco sono supportati fermamente, sebbene nessuno usi più quelle parti delle specifiche.
Matt Joiner

9

Qui, lasciami aggiustare queste funzioni:

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

Per quanto riguarda la motivazione dei parametri per fread()/ fwrite(), ho perso la mia copia di K&R molto tempo fa, quindi posso solo immaginare. Penso che una risposta probabile sia che Kernighan e Ritchie potrebbero aver semplicemente pensato che l'esecuzione di I / O binari sarebbe stata eseguita in modo più naturale su array di oggetti. Inoltre, potrebbero aver pensato che l'I / O a blocchi sarebbe stato più veloce / più facile da implementare o qualsiasi altra cosa su alcune architetture.

Anche se lo standard C lo specifica fread()e deve fwrite()essere implementato in termini di fgetc()e fputc(), ricorda che lo standard è nato molto dopo che C è stato definito da K&R e che le cose specificate nello standard potrebbero non essere state nelle idee dei designer originali. È anche possibile che le cose dette in "The C Programming Language" di K&R potrebbero non essere le stesse di quando il linguaggio è stato progettato per la prima volta.

Infine, ecco cosa ha da dire PJ Plauger fread()in "The Standard C Library":

Se la size (secondo) argomento è maggiore di uno, non è possibile determinare se la funzione legge anche size - 1caratteri aggiuntivi oltre a quanto riportato. Di regola, è meglio chiamare la funzione come fread(buf, 1, size * n, stream);invece di fread(buf, size, n, stream);

In modo malizioso, sta dicendo che fread()l'interfaccia è rotta. Per fwrite()lui osserva che "gli errori di scrittura sono generalmente rari, quindi questo non è un grave difetto" - un'affermazione con cui non sarei d'accordo.


17
In realtà spesso mi piace farlo nell'altro modo: fread(buf, size*n, 1, stream);se le letture incomplete sono una condizione di errore, è più freadsemplice fare in modo che restituisca semplicemente 0 o 1 piuttosto che il numero di byte letti. Quindi puoi fare cose come if (!fread(...))invece di dover confrontare il risultato con il numero richiesto di byte (che richiede codice C extra e codice macchina extra).
R .. GitHub SMETTA DI AIUTARE IL GHIACCIO

1
@R .. Assicurati solo di controllare che size * count! = 0 oltre a! Fread (...). Se size * count == 0, stai ottenendo un valore di ritorno zero su una lettura riuscita (di zero byte), feof () e ferror () non saranno impostati, e errno sarà qualcosa di assurdo come ENOENT, o peggio , qualcosa di fuorviante (e forse critico) come EAGAIN - molto confuso, soprattutto perché praticamente nessuna documentazione ti urla contro questo problema.
Pegasus Epsilon


1

Avere argomenti separati per dimensione e conteggio potrebbe essere vantaggioso su un'implementazione che può evitare di leggere record parziali. Se si utilizzassero letture a byte singolo da qualcosa come una pipe, anche se si utilizzassero dati in formato fisso, si dovrebbe consentire la possibilità che un record venga suddiviso in due letture. Se invece si potesse richiedere ad esempio una lettura non bloccante fino a 40 record di 10 byte ciascuno quando ci sono 293 byte disponibili, e fare in modo che il sistema restituisca 290 byte (29 record interi) lasciando 3 byte pronti per la lettura successiva, ciò sarebbe essere molto più conveniente.

Non so fino a che punto le implementazioni di fread possano gestire tale semantica, ma potrebbero certamente essere utili su implementazioni che potrebbero promettere di supportarle.


@PegasusEpsilon: se ad esempio un programma lo fa fread(buffer, 10000, 2, stdin)e l'utente digita newline-ctrl-D dopo aver digitato 18.000 byte, sarebbe bello se la funzione potesse restituire i primi 10.000 byte lasciando gli 8.000 rimanenti in sospeso per future richieste di lettura più piccole, ma ci sono eventuali implementazioni in cui ciò sarebbe accaduto? Dove verranno archiviati gli 8.000 byte in attesa di tali richieste future?
supercat

Dopo averlo appena testato, risulta che fread () non funziona in quello che considererei il modo più conveniente a questo proposito, ma poi reinserire i byte nel buffer di lettura dopo aver determinato una lettura breve è probabilmente un po 'più di quanto dovremmo aspettarci le funzioni della libreria standard comunque. fread () leggerà i record parziali e li inserirà nel buffer, ma il valore restituito specificherà quanti record completi sono stati letti e non ti dice nulla (il che è abbastanza fastidioso per me) su eventuali letture brevi estratte dallo stdin.
Pegasus Epsilon

... continua ... La cosa migliore che puoi fare è probabilmente riempire il tuo buffer di lettura con valori nulli prima di fread, e controllare il record dopo dove fread () dice che è finito per eventuali byte non nulli. Non ti aiuta particolarmente quando i tuoi record possono contenere null, ma se intendi utilizzaresize maggiore di 1, beh ... Per la cronaca, potrebbero esserci anche ioctl o altre sciocchezze che puoi applicare allo stream per farlo comportarsi diversamente, non l'ho approfondito.
Pegasus Epsilon

Inoltre ho cancellato il mio commento precedente a causa di imprecisione. Oh bene.
Pegasus Epsilon

@PegasusEpsilon: C è utilizzato su così tante piattaforme, che si adattano a comportamenti diversi. L'idea che i programmatori dovrebbero aspettarsi di utilizzare le stesse caratteristiche e garanzie su tutte le implementazioni ignora quella che era stata la caratteristica migliore di C: che il suo design avrebbe consentito ai programmatori di utilizzare caratteristiche e garanzie sulle piattaforme in cui erano disponibili. Alcuni tipi di flussi possono supportare facilmente pushback di dimensioni arbitrarie e avere il freadlavoro come descritto su tali flussi sarebbe utile se ci fosse un modo per identificare i flussi che funzionano in quel modo.
supercat

0

Penso che sia perché C manca di sovraccarico di funzioni. Se ce ne fossero, le dimensioni sarebbero ridondanti. Ma in C non puoi determinare la dimensione di un elemento dell'array, devi specificarne uno.

Considera questo:

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

Se si scrive un numero di byte accettato, è possibile scrivere quanto segue:

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

Ma è solo inefficiente. Avrai sizeof (int) volte più chiamate di sistema.

Un altro punto che dovrebbe essere preso in considerazione è che di solito non si desidera che una parte di un elemento dell'array venga scritta in un file. Vuoi l'intero numero intero o niente. fwrite restituisce un numero di elementi scritti con successo. Quindi, se scopri che sono scritti solo 2 byte bassi di un elemento, cosa faresti?

Su alcuni sistemi (a causa dell'allineamento) non è possibile accedere a un byte di un numero intero senza creare una copia e spostarsi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.