Rispettivamente leggono e scrivono nmemb elementi di dimensione size dal buffer ptr al file stream.
Entrambe le funzioni ritornano il numero di elementi letti o scritti, in caso di errore o fine del file viene restituito un numero di elementi inferiore al richiesto.
In genere si usano queste funzioni quando si devono trasferire su file blocchi di dati binari in maniera compatta e veloce; un primo caso di uso tipico è quello in cui si salva un vettore (o un certo numero dei suoi elementi) con una chiamata del tipo:
1: int WriteVect(FILE *stream, double *vec, size_t nelem) 2: { 3: int size, nread; 4: size = sizeof(*vec); 5: if ( (nread = fwrite(vec, size, nelem, stream)) != nelem) { 6: perror("Write error"); 7: } 8: return nread; 9: }
01: struct histogram { 02: int nbins; 03: double max, min; 04: double *bin; 05: } histo; 06: 07: int WriteStruct(FILE *stream, struct histogram *histo) 08: { 09: if ( fwrite(histo, sizeof(*histo), 1, stream) !=1) { 10: perror("Write error"); 11: } 12: return nread; 13: }
In realtà quello che conta nel trasferimento dei dati sono le dimensioni totali, che sono sempre pari al prodotto size * nelem; la sola differenza è che le funzioni non ritornano il numero di byte scritti, ma il numero di elementi.
La funzione fread legge sempre un numero intero di elementi, se incontra la fine del file l'oggetto letto parzialmente viene scartato (lo stesso avviene in caso di errore). In questo caso la posizione dello stream viene impostata alla fine del file (e non a quella corrispondente alla quantità di dati letti).
In caso di errore (o fine del file per fread) entrambe le funzioni restituiscono il numero di oggetti effettivamente letti o scritti, che sarà inferiore a quello richiesto. Contrariamente a quanto avviene per i file descriptor, questo segnala una condizione di errore e occorrerà usare feof e ferror per stabilire la natura del problema.
Benché queste funzioni assicurino la massima efficienza per il salvataggio dei dati, i dati memorizzati attraverso di esse presentano lo svantaggio di dipendere strettamente dalla piattaforma di sviluppo usata ed in genere possono essere riletti senza problemi solo dallo stesso programma che li ha prodotti.
Infatti diversi compilatori possono eseguire ottimizzazioni diverse delle strutture dati e alcuni compilatori (come il gcc) possono anche scegliere se ottimizzare l'occupazione di spazio, impacchettando più strettamente i dati, o la velocità inserendo opportuni padding per l'allineamento dei medesimi generando quindi output binari diversi. Inoltre altre incompatibilità si possono presentare quando entrano in gioco differenze di architettura hardware, come la dimensione del bus o la modalità di ordinamento dei bit o il formato delle variabili in floating point.
Per questo motivo quando si usa l'input/output binario occorre sempre prendere le opportune precauzioni (in genere usare un formato di più alto livello che permetta di recuperare l'informazione completa), per assicurarsi che versioni diverse del programma siano in grado di rileggere i dati tenendo conto delle eventuali differenze.
Le glibc definiscono altre due funzioni per l'I/O binario, fread_unlocked e fwrite_unlocked che evitano il lock implicito dello stream, usato per dalla librerie per la gestione delle applicazioni multi-thread (si veda sez. 7.3.3 per i dettagli), i loro prototipi sono:
Le funzioni sono identiche alle analoghe fread e fwrite ma non acquisiscono il lock implicito sullo stream.