2015-11-06 16 views
6

Per il mio incarico, sono obbligato a usare fread/fwrite. Ho scrittofread() una struttura in c

#include <stdio.h> 
#include <string.h> 

struct rec{ 
    int account; 
    char name[100]; 
    double balance; 
}; 

int main() 
{ 
    struct rec rec1; 
    int c; 

    FILE *fptr; 
    fptr = fopen("clients.txt", "r"); 

    if (fptr == NULL) 
     printf("File could not be opened, exiting program.\n"); 
    else 
    { 
     printf("%-10s%-13s%s\n", "Account", "Name", "Balance"); 
     while (!feof(fptr)) 
     { 
      //fscanf(fptr, "%d%s%lf", &rec.account, rec.name, &rec.balance); 
      fread(&rec1, sizeof(rec1),1, fptr); 
      printf("%d %s %f\n", rec1.account, rec1.name, rec1.balance); 
     } 
     fclose(fptr); 
    } 
    return 0; 
} 

file di Clients.txt

 
100 Jones 564.90 
200 Rita 54.23 
300 Richard -45.00 

uscita

 
Account Name   Balance 
540028977 Jones 564.90 
200 Rita 54.23 
300 Richard -45.00╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠ 
╠╠ü☻§9x°é -92559631349317831000000000000000000000000000000000000000000000.000000 

Press any key to continue . . . 

posso fare questo con fscanf (che Ive commentata), ma sto necessario per utilizzare fread/fwrite.

  1. Perché inizia con un numero enorme per l'account di Jone?
  2. Perché c'è spazzatura dopo? Non dovrebbe smettere di farlo?
  3. Esistono alcuni inconvenienti che utilizzano questo metodo? o il metodo fscanf?

Come posso risolvere questi? Molte grazie in anticipo

+4

Si prega di vedere [Perché "mentre (! Feof (file))" ha sempre torto?] (Http://stackoverflow.com/q/5431941/2173917) –

+0

Si prega di leggere [Perché è 'while (! Feof (fptr)) 'sempre sbagliato] (http://stackoverflow.com/a/26557243/1983495). –

+4

fread legge binario crudo. Il tuo client.txt è un file di testo, quindi non ha senso leggere questo file come dati binari non elaborati. –

risposta

4

Come dicono i commenti, fread legge i byte nel file senza alcuna interpretazione. Il file clients.txt consiste di 50 caratteri, 16 nella prima riga più 14 nella seconda più 18 nella terza riga, più due caratteri di nuova riga. (Il tuo client.txt non contiene una nuova riga dopo la terza riga, come vedrai presto.) Il carattere di nuova riga è un singolo byte \n su macchine UNIX o Mac OS X, ma (probabilmente) due byte \r\n su macchine Windows - quindi 50 o 51 caratteri. Ecco la sequenza di byte ASCII esadecimale:

3130 3020 4a6f 6e65 7320 3536 342e 3930  100 Jones 564.90 
0a32 3030 2052 6974 6120 3534 2e32 330a  \n200 Rita 54.23\n 
3330 3020 5269 6368 6172 6420 2d34 352e  300 Richard -45. 
3030          00 

vostri fread economico copie questi byte senza alcuna interpretazione direttamente nel tuo struttura di dati rec1. Tale struttura inizia con int account;, che dice di interpretare i primi quattro byte come int. Come uno dei commenti annotati, si sta eseguendo il programma su una macchina little-endian (molto probabilmente una macchina Intel), quindi il byte meno significativo è il primo e il byte più significativo è il quarto. Pertanto, il tuo fread ha detto di interpretare la sequenza di quattro caratteri ASCII "100 " come numero intero a quattro byte 0x20303031, che equivale, in decimale, a 540028977. Il prossimo membro della tua struttura è char name[100];, il che significa che i successivi 100 byte di dati in rec1 saranno il name. Ma allo fread è stato detto di leggere i byte sizeof(rec1)=112 (account da 4 byte, nome da 100 byte, saldo da 8 byte). Poiché il tuo file ha solo 50 (o 52 caratteri), fread sarà stato in grado di riempire solo quel numero di byte di rec1. Il valore di ritorno di fread, se non l'avessi scartato, ti avrebbe detto che la lettura non era sufficiente al numero di byte richiesti. Dal momento che si colpisce EOF, la chiamata feof interrompe il ciclo dopo il primo passaggio, dopo aver consumato l'intero file in un solo fiato.

Tutti i tuoi output sono stati prodotti dalla prima e unica chiamata a fprintf. Il numero 540028977 e il seguente spazio sono stati prodotti dallo "%d " e dall'argomento rec1.account. Il bit successivo è solo parzialmente determinato, e sei stato fortunato: l'identificatore "%s" e l'argomento corrispondente rec1.name stamperanno i caratteri successivi come ASCII fino a quando non verrà trovato un byte \0.Pertanto, l'output inizierà con 50-4 (o 52-4) di caratteri rimanenti del file, incluse le due nuove righe, e potenzialmente continuerà all'infinito, perché non ci sono byte \0 nel file (o in qualsiasi file di testo), il che significa che dopo aver stampato l'ultimo carattere del tuo file, quello che stai vedendo è qualunque cosa si sia verificata nella variabile automatica rec1 all'avvio del programma. (Questo tipo di output non intenzionale è simile al famoso bug heartbleed in OpenSSL.) Sei stato fortunato che la spazzatura includeva un byte \0 dopo solo poche decine di caratteri. Si noti che printf non ha modo di sapere che rec1.name è stato dichiarato essere solo un array di 100 byte - ha ottenuto solo il puntatore all'inizio di name - era responsabilità dell'utente garantire che rec1.name contenesse un byte di terminazione \0 e non si fatto questo

Possiamo dire un po 'di più. Il numero -9.2559631349317831e61 (che è piuttosto brutto nel formato "%f") è il valore di rec1.balance. Gli 8 byte per il valore double su una macchina IEEE 754 (come il tuo Intel e tutti i computer moderni) sono nell'esagono 0xcccccccccccccccc. Sessantaquattro del peculiare simbolo vengono visualizzati nell'output "%s" corrispondente a rec1.name, mentre restano solo 100-46 = 54 caratteri dei 100, quindi l'uscita "%s" ha esaurito la fine di rec1.name e include rec1.balance nell'affare e noi impara che il tuo programma terminale ha interpretato il carattere non ASCII 0xcc come . Esistono molti modi per interpretare byte più grandi di 127 (0x7f); in latino-1 sarebbe stato &Igrave; per esempio. Il carattere grafico è la rappresentazione del byte 0xcc (204) nell'antico set di caratteri MS-DOS, la tabella codici di Windows 437. Non solo si esegue su una macchina Intel, si tratta di una macchina Windows (ovviamente la possibilità più probabile iniziare con).

Questo risponde alle vostre prime due domande. Non sono sicuro di capire la tua terza domanda. Gli "svantaggi" che spero siano ovvi.

Per quanto riguarda come risolvere il problema, non esiste un modo ragionevolmente semplice per leggere e interpretare un file di testo utilizzando fread. Per fare ciò, è necessario duplicare gran parte del codice nella funzione libcfscanf. L'unico modo sensato è di usare prima fwrite per creare un file binario; quindi fread funzionerà naturalmente per rileggerlo. Quindi ci devono essere due programmi: uno per scrivere un file binario clients.bin e un secondo per rileggerlo. Naturalmente, ciò non risolve il problema di dove i dati per quel primo programma dovrebbero provenire in primo luogo. Potrebbe venire dalla lettura clients.txt utilizzando fscanf. Oppure potrebbe essere incluso nel codice sorgente del programma fwrite, ad esempio per inizializzare un array di struct rec come questo:

struct rec recs[] = {{100, "Jones", 564.90}, 
        {200, "Rita", 54.23}, 
        {300, "Richard", -45.00}}; 

Oppure potrebbe venire dalla lettura di un database MySQL, o ... L'unico posto che è improbabile che abbia origine in un file binario (facilmente) leggibile con fread.

+0

Wow, grazie per aver dedicato del tempo a spiegare ogni parte molto ampiamente! – user153882