Προς το περιεχόμενο

Εγγραφη και αναγνωση binary αρχειων απο C


panoupanou

Προτεινόμενες αναρτήσεις

Ναι, ομως αμα στο text file βαλουμε:

>for(;i<10000000;i++){
tmp = rand() % 1000;
fprintf(pf,"%d\n",tmp);
}

Mειωνεται σημαντικα κι ο χρονος (στο μισοο) και το μεγεθος στα ~47ΜΒ, δηλαδη εχει σημασια το ευρως και η κατανομη των ακεραιων

 

Σωστά. Αν έχει λιγότερα "γράμματα" (στην text απόδοση) κάθε αριθμός τότε (θεωρητικά) κάνει λιγότερο χρόνο να γραφτεί και πιάνει και λιγότερο χώρο.

 

Πάντως νομίζω ότι γίνεται ένα λάθος που το έχω παρατηρήσει ξανά στο παρελθόν (συγχωρέστε με αν κάνω λάθος :D )

Ερωτήσεις σαν και αυτές εμένα προσωπικά πιο πολύ μου δημιουργούν την αίσθηση οτι ο ts δεν έχει ξεκάθαρο πλήρως το τι σημαίνει γράφω binary data --- γράφω text παρά ότι προβληματίζεται γύρο απο τη βαθύτερη μηχανική και την απόδοση γύρω απο αυτό.

 

Συνεπώς η ανάλυση πάνω στο δεύτερο (καλή και θεμιτή) ίσως αποπροσανατολίζει τον (κάθε φορά και θεωρητικά πάντα) ts.

 

Μήπως θα ήταν καλό δηλαδή να γίνεται μια μικρή προσπάθεια (όταν δεν είναι πασιφανές) "εύρεσης" του τι δεν έχει καταλάβει ο κάθε φορά ts και απάντησης πάνω σε αυτό. (Επαναλαμβάνω, στο συγκεκριμένο θέμα μπορεί να έχω λάθος, μπορεί όντως να γνωρίζει πλήρως)

  • Like 2
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

  • Απαντ. 60
  • Δημ.
  • Τελ. απάντηση

Συχνή συμμετοχή στο θέμα

Δημοφιλείς Ημέρες

Εγώ πάντως τον ρώτησα 2 φορές (μια να μας διευκρινήσει τι εννοεί και μια να μας πει με ποιον τρόπο κάνει ότι κάνει). ;)

 

Ορίστε κι ένας τρόπος που επισπεύδει το file-access....

 

>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#include <stdbool.h>            // c99
#include <stdint.h>            // c99

#define MAX_NUMS    10000000    // 10 million

/* ------------------------------------------------------- */
int *bufInt_init( void )
{
   int *buf = malloc(MAX_NUMS * sizeof(int));
   if ( NULL == buf )
       return NULL;

   for (int i=0; i < MAX_NUMS; i++)
       buf[i] = rand() % INT_MAX;

   return buf;
}

/* ------------------------------------------------------- */
bool write_bin( const int *buf )
{
   FILE *fp = NULL;

   if ( !buf )
       return false;

   if ( NULL == (fp=fopen("_bin.dat", "wb")) )
       return false;

// ******** START TIMING HERE
   if ( MAX_NUMS != fwrite( buf, sizeof(int), MAX_NUMS, fp) ) {
       fclose( fp );
       return false;
   }
// ******** END TIMING HERE

   fclose(fp);

   return true;
}

/* -------------------------------------------------------
*
* -------------------------------------------------------
*/
int main( void )
{
   int  *buf = NULL;

   printf( "RAND_MAX = %d\n", RAND_MAX );
   srand( time(NULL) );

   // init buf with MAX_NUMS random integers
   if ( NULL == (buf = bufInt_init()) ) {
       puts( "*** could not bufInt_init()" );
       goto exit_app;
   }

   // write binary buffer
   if ( !write_bin( buf ) ) {
       puts( "*** could not write_bin)()" );
       goto exit_app;
   }


exit_app:
   if ( buf )
       free( buf );
   system("pause");
   exit( EXIT_SUCCESS );
}

 

Σώζει μονοκόμματα όλο το buffer στο αρχείο σε μια μόνο κίνηση (@moukoublen από περιέργεια κάνε το αν θες timing κι αυτό, έχω βάλει σχόλια για το που... αν και όταν μπαίνει i/o στη μέση τα timings την κάνουν για άλλη παραλία :lol:).

 

Αντί δηλαδή να σώζει τα 10.000.000 νούμερα ένα-ένα στο αρχείο, τα βάζει πρώτα σε ένα buffer και κατόπιν σώζει μονοκόμματα όλο το buffer.

 

ΣΗΜΕΙΩΣΗ: Αν στον compiler σας το RAND_MAX είναι μικρότερο του 10.000.000 η rand() θα δώσει πολύ μικρότερα νούμερα από ότι χωράνε σε 4 bytes ... για αυτό έχω βάλει να το το τυπώνει. Π.χ. στον mingw βγάζει έχει πολύ μικρό value η RAND_MAX (εγώ το έκανα compile με 64μπιτη Pelles-C, για να παράγει μεγάλα νούμερα (από 0 έως INT_MAX-1).

 

EDIT:

 

Το επόμενο βήμα είναι να κάνουμε το ίδιο πράγμα και για ένα char *buf, και να το τυπώσουμε κι αυτό μονοκόμματα με fwrite() ... αλλά ανοιγμένο το αρχείο σε "text" mode. Είμαι στη δουλειά όλως τώρα και δεν μπορώ να το κάνω.

 

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Σώζει μονοκόμματα όλο το buffer στο αρχείο σε μια μόνο κίνηση (@moukoublen από περιέργεια κάνε το αν θες timing κι αυτό, έχω βάλει σχόλια για το που... αν και όταν μπαίνει i/o στη μέση τα timings την κάνουν για άλλη παραλία :lol:).

 

Αντί δηλαδή να σώζει τα 10.000.000 νούμερα ένα-ένα στο αρχείο, τα βάζει πρώτα σε ένα buffer και κατόπιν σώζει μονοκόμματα όλο το buffer.

 

Όντως. Όσες φορές το έτρεξα δεν μπόρεσε να καταγράψει πάνω από ένα δευτερόλεπτο!

(πρέπει να κάνω κάτι λάθος στην εκτύπωση του difftime γιατί μου τυπώνει -ή επιστρέφει η συνάρτηση δε ξέρω- μόνο ακέραιες τιμές. Τέλος πάντων...)

 

( gcc 4.6.3 x86_64 )

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δεν είδα πως το κάνεις time, ίσως θες ρουτίνες που μετράνε nanoseconds, microseconds ή έστω milliseconds :P

 

Λοιπόν, ολοκληρωμένος και ο κώδικας και για text, μόνο που για να γεμίζει το buffer με την snprintf() κάνει ... ώωωωωωωρα :P

 

 

Βασικά πρώτα το έκανα με realloc, όπου εκεί δεν τελειώνει ποτέ (λογικό) οπότε το έκανα αλλιώς. Υποθέτοντας πως κανένας ακέραιος (ακόμα και 64μπιτος) δεν ξεπερνάει τα 20 ψηφία, κάνω alloc απευθείας 10.000.000 * 21 bytes, και μετά του πετάω την snprintf(). Κατόπιν για να το επισπεύσω, αντί για strcpy(buf, temp) κρατάω έναν δείκτη (cp) στο τέλος του buf σε κάθε iteration και κάνω εκεί memcpy() το temp. Μπορεί να επισπευθεί ακόμα περισσότερο το γέμισμα του buffer με χρήση της sprintf() αντί για την snprintf().

 

Έχω αφήσει και την ρουτίνα με το realloc με ένα under_bar μπροστά από το όνομα της, αλλά σέρνεται απίστευτα.

 

Ακόμα και η κανονική αργεί αρκετά. Το παραγόμενο αρχείο είναι κοντά στα 100mb, too big για να το ανοίξει το Notepad++ (εγώ το άνοιξα με το cc-hexview). Είναι κανονικό text αρχείο (αντί για αλλαγές γραμμής μετά από κάθε νούμερο, έχω βάλει space... μπορείτε να το αλλάξετε στην snprintf() με '\n' αν θέλετε.

 

Αν σας αργεί πολύ το γέμισμα του bufChr μειώστε το MAX_NUMS (για να επιβεβαιώσετε πως παρόλο που χρησιμοποιήθηκε η fwrite() παράγεται κανονικό text αρχείο).

 

 

 

>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <limits.h>
#include <stdbool.h>            // c99
#include <stdint.h>            // c99

#define MAX_NUMS    10000000    // 10 million

/* ------------------------------------------------------- */
char *bufChar_init( void )
{
   char *cp = NULL, temp[21] = {'\0'};
   char *buf = calloc( MAX_NUMS * 21, sizeof(char) );
   if ( NULL == buf )
       return NULL;

   cp = buf;
   for (int i=0; i < MAX_NUMS; i++)
   {
       int nchars = snprintf( temp, 21, "%d ", rand() % INT_MAX );
       if ( nchars < 0 || nchars >= 21 )
           goto ret_failure;
       memcpy( cp, temp, (nchars) * sizeof(char) );
       cp += nchars;
   }
   *--cp = '\0';

   return buf;

ret_failure:
   if ( buf )
       free(buf);
   return NULL;
}

/**** με realloc() ... δεν τελειώνει ποτέ  ****/
char *_bufChar_init( void )
{
   char *buf = NULL, *try = NULL;
   char temp[30] = {'\0'};
   size_t len = 0;

   // alloc a null c-string
   buf = calloc(1, sizeof(char) );
   if ( NULL == buf )
       return NULL;

   for (int i=0; i < MAX_NUMS; i++ )
   {
       int nchars = snprintf( temp, 30, "%d ", rand() % INT_MAX );
       if ( nchars < 0 || nchars >= 30 )
           goto ret_failure;
       len = 1 + nchars + strlen(buf);
       try = realloc( buf, len * sizeof(char) );
       if ( NULL == try )
           goto ret_failure;

       buf = try;
       strcat(buf, temp);
   }

   return buf;

ret_failure:
   if ( buf )
       free(buf);
   return NULL;
}

/* ------------------------------------------------------- */
bool write_text( const char *buf )
{
   FILE *fp  = NULL;

   if ( !buf )
       return false;

   if ( NULL == (fp=fopen("_text.dat", "w")) )
       return false;

   size_t len = strlen(buf);
// ******** START TIMING HERE
   if ( len != fwrite( buf, sizeof(char), len, fp) ) {
       fclose( fp );
       return false;
   }
// ******** END TIMING HERE

   fclose(fp);

   return true;
}

/* ------------------------------------------------------- */
int *bufInt_init( void )
{
   int *buf = malloc(MAX_NUMS * sizeof(int));
   if ( NULL == buf )
       return NULL;

   for (int i=0; i < MAX_NUMS; i++)
       buf[i] = rand() % INT_MAX;

   return buf;
}

/* ------------------------------------------------------- */
bool write_bin( const int *buf )
{
   FILE *fp = NULL;

   if ( !buf )
       return false;

   if ( NULL == (fp=fopen("_bin.dat", "wb")) )
       return false;

// ******** START TIMING HERE
   if ( MAX_NUMS != fwrite( buf, sizeof(int), MAX_NUMS, fp) ) {
       fclose( fp );
       return false;
   }
// ******** END TIMING HERE

   fclose(fp);

   return true;
}

/* -------------------------------------------------------
*
* -------------------------------------------------------
*/
int main( void )
{
   int  *buf = NULL;
   char *bufChr = NULL;

   printf( "RAND_MAX = %d\n", RAND_MAX );
   srand( time(NULL) );

   // init buf with MAX_NUMS random integers
   if ( NULL == (buf = bufInt_init()) ) {
       puts( "*** could not bufInt_init()" );
       goto exit_app;
   }
   // write binary buffer
   if ( !write_bin( buf ) ) {
       puts( "*** could not write_bin()" );
       goto exit_app;
   }


   // init bufChr with MAX_NUMS random integers
   if ( NULL == (bufChr = bufChar_init()) ) {
       puts( "*** could not bufChr_init()" );
       goto exit_app;
   }
   // write text file
   if ( !write_text( bufChr ) ) {
       puts( "*** could not write_text()" );
       goto exit_app;
   }

exit_app:
   if ( buf )
       free( buf );
   if ( bufChr )
       free( bufChr );

   system("pause");
   exit( EXIT_SUCCESS );
}

 

 

 

Εννοείται πως όλη αυτή η βαβούρα είναι για να επισπευθεί το γράψιμο/άνοιγμα του text-αρχειου, μιας και αυτό είναι το θέμα μας.

 

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Αυτά τα προγράμματα όμως το μόνο που κάνουν είναι να σώζουν το αρχείο οπότε η διαφορά φαίνεται θεόρατη. Σε ένα πραγματικό σενάριο θα γίνεται κάποια εργασία πάνω σε αυτούς τους αριθμούς (ή οτιδήποτε τύπο έχουμε) οπότε μπορεί με τη μία μέθοδο να έχουμε συνολικό χρόνο 71sec και και με την άλλη να έχουμε 72sec το οποίο είναι αμελητέο.

 

Δεν θα είναι καλύτερα, _ΑΝ_ το πρόγραμμα είναι πιο αργό από ό,τι αρέσει στον OP, να κάνει profiling να δει τι ποσοστό της αργοπορίας οφείλεται στην ανάγνωση / εγγραφή ?

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Για αυτό ρώτησα τον topic-starter να μας πει αν θέλει τι ακριβώς θέλει να κάνει.

 

Πάντως σε γενικές γραμμές δεν υπάρχει καμία σύγκριση μεταξύ μονοκόμματων και τμηματικών fread/fwrite, με την διαφορά προφανώς υπερ των μονοκόμματων.

 

Αν δεν υπάρχει ιδιαίτερος λόγος να σώζει/διαβάζει σε αυτό το text-strings mode, το binary handling δίνει πολύ speed (χάνει όμως portability).

 

EDIT:

 

Πάντως στην πράξη δεν είναι και τόσο δύσχρηστο το binary handling, αφού ακόμα και τα struct σου τα διαβάζεις έτοιμα από το αρχείο και κατόπιν τα διαχειρίζεσαι κανονικά.

 

Ενσωματώνεις κι ένα CSV Import/Export στο πρόγραμμά σου (piece of cake code) και καθαρίζεις και με το θέμα του portability ;)

 

 

 

 

 

>
struct mitsos {
   char onoma[30];
   char epitheto[50];
   int hlikia;
   int bathmos;
};

1)
fwrite(tade_variable, sizeof(struct mitsos), 1, myfile);

2)
... 
fprintf(myfile, "%d\n", tade.hlikia);
...

 

Ας πούμε ότι έχεις την παραπάνω δομή. Στην "binary" μέθοδο γράφεις με την fwrite όλη τη δομή μαζί με το padding και τα πάντα (ή έστω το κάθε πεδίο ένα-ένα με fwrite(tade.hlikia, sizeof(int), 1, myfile)) ενώ στην "text" μέθοδο γράφεις όλα τα πεδία σαν string και δεν μπλέκεις με padding, endianness, κτλ. Την "text" μέθοδο δηλαδή σκέψου την σαν μια πολύ απλοϊκή μπακάλικη serialisation

...

 

Btw, τώρα που ξαναδιαβάζω κι αυτό, άσχετο βεβαία με την εξέλιξη του νήματος, αλλά για το padding κάτι σαν το παρακάτω πως σας φαίνεται..

 

>

typedef struct Student {
   enum {
     STUD_ONOMA = 0,
     STUD_EPITHETO = 30,
     STUD_HLIKIA = 80,
     STUD_BATHMOS = STUD_HLIKIA + sizeof(int),
     STUD_END = STUD_BATHMOS + sizeof(int)
   };
   uint8_t data[OFST_END];
}Student;
...

Student stud;
printf( "Enter name: " );
scanf( "%s", &stud.data[sTUD_ONOMA] );
printf( "Enter grade: " );
scanf( "%d", &stud.data[sTUD_BATHMOS] );

...
printf( "%s's grade is %d\n", stud.data[sTUD_ONOMA}, stud.data[sTUD_BATHMOS] );

 

Εντάξει, το 'γραψα πολύ μπαμ-μπαμ (δεν θυμάμαι καν αν επιτρέπεται nested enum μέσα σε struct) αλλά περισσότερο για την κεντρική ιδέα το έγραψα (γιατί νομίζω πως δεν είναι εφικτό σε όλες τις πλατφόρμες/compilers να απενεργοποιήσει κανείς το padding)

 

Επίσης, δεν γλιτώνουμε το endianess.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δεν είδα πως το κάνεις time, ίσως θες ρουτίνες που μετράνε nanoseconds, microseconds ή έστω milliseconds :P

 

Με ένα πρόχειρο διάβασμα είδα οτι σε ANSI C δεν μπορείς να μετρήσεις με τα time κάτω από δευτερόλεπτο παρά μόνο με κάποιες συναρτήσεις clock_gettime και άλλη δομή, τέλος πάντων δε ξέρω στη C99 αν γίνεται κάπως πιο απλά, θα κοιτάξω ίσως με τα clocks.

 

Με τα time_p λοιπόν έχεις "κβάντο χρόνου" ( :P ) το δευτερόλεπτο και το έκανα ως εξής:

 

 

>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <limits.h>
#include <stdbool.h>                    // c99
#include <stdint.h>                     // c99
#define MAX_NUMS        10000000        // 10 million

/* ------------------------------------------------------- */
int *bufInt_init( void )
{
   int *buf = malloc(MAX_NUMS * sizeof(int));
   if ( NULL == buf )
       return NULL;

   for (int i=0; i < MAX_NUMS; i++)
       buf[i] = rand() % INT_MAX;

   return buf;
}

/* ------------------------------------------------------- */
bool write_bin( const int *buf, time_t *time_start, time_t *time_stop )
{
   FILE *fp = NULL;

   if ( !buf )
       return false;

   if ( NULL == (fp=fopen("_bin.dat", "wb")) )
       return false;

   time(time_start);
   if ( MAX_NUMS != fwrite( buf, sizeof(int), MAX_NUMS, fp) ) {
       fclose( fp );
       return false;
   }
   time(time_stop);

   fclose(fp);

   return true;
}

/* -------------------------------------------------------
*
* -------------------------------------------------------
*/
int main( void )
{
   time_t time_start, time_stop;
   int  *buf = NULL;

   printf( "RAND_MAX = %d\n", RAND_MAX );
   printf( "INT_MAX  = %d\n", INT_MAX );
   srand( time(NULL) );

   // init buf with MAX_NUMS random integers
   if ( NULL == (buf = bufInt_init()) ) {
           puts( "*** could not bufInt_init()" );
           goto exit_app;
   }


   // write binary buffer
   if ( !write_bin( buf, &time_start, &time_stop ) ) {
       puts( "*** could not write_bin)()" );
       goto exit_app;
   }
   printf("Time: %.2f\n", difftime(time_stop, time_start));


exit_app:
   if ( buf )
       free( buf );
   //system("pause");
   exit( EXIT_SUCCESS );
}

 

 

 

Για αυτό ρώτησα τον topic-starter να μας πει αν θέλει τι ακριβώς θέλει να κάνει.

 

Πάντως σε γενικές γραμμές δεν υπάρχει καμία σύγκριση μεταξύ μονοκόμματων και τμηματικών fread/fwrite, με την διαφορά προφανώς υπερ των μονοκόμματων.

 

Αν δεν υπάρχει ιδιαίτερος λόγος να σώζει/διαβάζει σε αυτό το text-strings mode, το binary handling δίνει πολύ speed (χάνει όμως portability).

 

Ακριβώς. Αντίστοιχα και στο read

 

Read Binary

 

 

>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define MAX_NUMS	    10000000	    // 10 million

int *bufInt_init( void )
{
   int *buf = malloc(MAX_NUMS * sizeof(int));
   if ( NULL == buf )
    return NULL;
   return buf;
}
int main(int argc, char **argv)
{
   time_t start, stop;
FILE *pf;
   pf = fopen("data.bin","rb");
   int *buf;
  
   // init buf
   if ( NULL == (buf = bufInt_init()) ) {
	    puts( "*** could not bufInt_init()" );
	    goto exit_app;
   }
  
   time(&start);
  
   fread(buf,sizeof(int),MAX_NUMS, pf);
  
   time(&stop);
  
  
   fclose(pf);
   printf("Time: %.2f\n", difftime(stop, start));
  
exit_app:
   if ( buf )
    free( buf );
   return 0;
}

Time: 0.00 βγάζει (δηλαδή κάτω απο ένα sec)

 

 

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

 

Με ένα πρόχειρο διάβασμα είδα οτι σε ANSI C δεν μπορείς να μετρήσεις με τα time κάτω από δευτερόλεπτο παρά μόνο με κάποιες συναρτήσεις

clock_gettime και άλλη δομή, τέλος πάντων δε ξέρω στη C99 αν γίνεται κάπως πιο απλά, θα κοιτάξω ίσως με τα clocks.

 

Μπορείς να χρησιμοποιήσεις την gettimeofday που προτείνουν και στο νήμα του SO. Όποια συνάρτηση και να χρησιμοποιηθεί άλλωστε, το αποτέλεσμα της μέτρησης θα είναι αντιπροσωπευτικό μόνο για τον συγκεκριμένο compiler στη συγκεκριμένη πλατφόρμα οπότε δεν πειράζει να μην υπάρχει στην ANSI-C (παραβλέποντας φυσικά αυτό που είπαμε πριν ότι δεν μετράμε καν ένα πραγματικό σενάριο).

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@migf1

Βλέπω καλά; GOTO;;;;;;;;;;;; :P

 

Oh yes , από τις ελάχιστες θεμιτές (και πολυ-χρησιμοποιούμενες) χρήσεις της goto. Όταν δηλαδή θέλουμε να κάνουμε cleanup και τερματισμό του προγράμματος.

 

Αν σε... ξάφνιασε το goto, που να δες και τον κώδικα που ακολουθεί (σε συνέχεια του nested enum που έγραφα παραπάνω για το paddding) ...

 

 

 

>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <limits.h>
#include <stdbool.h>        // c99
#include <stdint.h>        // c99

#define FNAME            "_class.dat"

#define MAXINPUT        (255+1)
#define MAX_STUDENTS        1000000

#define int_( recbuf )        ( *(int *) &(recbuf) )
#define string_( recbuf )    ( (char *) &(recbuf) )


enum StudOffset{
   FNAME_MAXLEN    = 30,
   LNAME_MAXLEN    = 50,
   // buffer offsets of fields
   STUD_ID        = 0,                // int id;
   STUD_FNAME     = STUD_ID + sizeof(int),    // char fname[FNAME_MAXLEN];
   STUD_LNAME     = STUD_FNAME + FNAME_MAXLEN,    // char lname[LNAME_MAXLEN];
   STUD_AGE     = STUD_LNAME + LNAME_MAXLEN,    // int age;
   STUD_GRADE     = STUD_AGE   + sizeof(int),    // int grade;
   STUD_SIZE     = STUD_GRADE + sizeof(int)
};

typedef struct Student {
       int8_t recbuf[sTUD_SIZE];    // the un-padded buffer
}Student;

/* --------------------------------------------------------------------------------- */
int prompt_for_string( const char *prompt, char str[], int n )
{
   int i = 0;

   if ( prompt ) {
       printf( "%s", prompt );
       fflush( stdout );
   }

   if ( !str )                    // sanity check
       return 0;

   for (i=0; i < n && '\n' != (str[i] = getchar()); i++ )
       ; // void

   // flush any remaining chars from stdin
   if ( '\n' != str[i] )                // n reached without '\n
       while ('\n' != getchar())        //... flush remaining chars
           ; // void

   str[i] = '\0';                    // null termination of str

   return i;                         // numbers of chars stored
}

/* ------------------------------------------------------- */
int prompt_for_int( const char *prompt, int *integer )
{
   char input[MAXINPUT] = {'\0'};

   if ( prompt ) {
       printf( "%s", prompt );
       fflush( stdout );
   }

   prompt_for_string(NULL, input, MAXINPUT );
   if ( 1 != sscanf( input, "%d", integer ) )
       return INT_MIN;

   return *integer;
}

/* ------------------------------------------------------- */
bool student_generate( Student *student )
{
   int8_t *recbuf = NULL;

   if ( !student )
       return false;

   recbuf = student->recbuf;

   memset( string_( recbuf[sTUD_FNAME] ), 0, FNAME_MAXLEN );
   for (int i=0; i < FNAME_MAXLEN-1; i++ ) {
       char c = 'A' + rand() % 27;
       memcpy( string_( recbuf[sTUD_FNAME+i] ), &c, sizeof(char) );
   }

   memset( string_( recbuf[sTUD_LNAME] ), 0, LNAME_MAXLEN );
   for (int i=0; i < LNAME_MAXLEN-1; i++ ) {
       char c = 'A' + rand() % 27;
       memcpy( string_( recbuf[sTUD_LNAME+i] ), &c, sizeof(char) );
   }

   int temp = 18 + rand() % 11;
   memcpy( &int_( recbuf[sTUD_AGE] ), &temp, sizeof(int) );


   temp = 55 + rand() % 46;
   memcpy( &int_( recbuf[sTUD_GRADE] ), &temp, sizeof(int) );

   return true;
}

/* ------------------------------------------------------- */
bool class_generate( Student *class )
{
   if ( !class )
       return false;

   for (int i=0; i < MAX_STUDENTS; i++) {
       student_generate( &class[i] );
       memcpy( &int_(class[i].recbuf[sTUD_ID]), &i, sizeof(int) );
   }

   return true;
}

/* ------------------------------------------------------- */
bool class_read( Student *class )
{
   if ( !class )
       return false;

   for (int i=0; i < MAX_STUDENTS; i++)
   {
       int8_t *recbuf = class[i].recbuf;

       memcpy( &int_(recbuf[sTUD_ID]), &i, sizeof(int) );
       prompt_for_string(
           "First name: ",
           string_( recbuf[sTUD_FNAME] ),
           FNAME_MAXLEN
       );
       prompt_for_string(
           "Last name: ",
           string_( recbuf[sTUD_LNAME] ),
           LNAME_MAXLEN
       );
       prompt_for_int(
           "Age: ",
           &int_( recbuf[sTUD_AGE] )
       );
       prompt_for_int(
           "Grade: ",
           &int_( recbuf[sTUD_GRADE] )
       );
       putchar( '\n' );
   }

   return true;
}
/* ------------------------------------------------------- */
bool class_print( Student *class, int nstuds )
{
   if ( !class )
       return false;

   printf( "%d Total Students\n", nstuds );
   for (int i=0; i < nstuds; i++)
   {
       int8_t *recbuf = class[i].recbuf;

       printf( "student-%d\n\tfname: %s\n\tlname: %s\n\tage: %d\n\tgrade: %d",
           int_( recbuf[sTUD_ID] ),
           string_( recbuf[sTUD_FNAME] ),
           string_( recbuf[sTUD_LNAME] ),
           int_( recbuf[sTUD_AGE] ),
           int_( recbuf[sTUD_GRADE] )
       );
       putchar( '\n' );
   }

   return true;
}
/* ------------------------------------------------------- */
int class_save( Student *class, const char *fname )
{
   FILE *fp = NULL;
   size_t ret;

   if ( !class || !fname )
       return -1;

   fp = fopen( fname, "wb" );
   if (NULL == fp )
       return -1;

   ret = fwrite( class, sizeof(Student), MAX_STUDENTS, fp );
   if ( MAX_STUDENTS != ret ) {
       fclose(fp);
       return 0;
   }

   fclose(fp);
   return ret;
}
/* ------------------------------------------------------- */
int class_load( Student *class, const char *fname )
{
   FILE *fp = NULL;
   size_t ret;

   if ( !class || !fname )
       return -1;

   fp = fopen( fname, "rb" );
   if (NULL == fp )
       return -1;

   ret = fread( class, sizeof(Student), MAX_STUDENTS, fp );
   if ( MAX_STUDENTS != ret ) {
       fclose(fp);
       return 0;
   }

   fclose(fp);
   return ret;
}
/* -------------------------------------------------------
*
* -------------------------------------------------------
*/
int main( void )
{
   int nstuds = 0;
   Student *class = calloc(MAX_STUDENTS, sizeof(Student));
   if ( NULL == class )
       exit(1);

   srand( time(NULL) );

    nstuds = class_load( class, FNAME );
   if ( nstuds < 1 ) {
//        class_read( class );
       class_generate( class );
       nstuds = MAX_STUDENTS;
   }

//    class_print( class, nstuds );
   printf( "%d records loaded, %zu bytes each, %zu bytes in total\n",
       nstuds, sizeof(Student), nstuds * sizeof(Student)
   );

   class_save( class, FNAME );

   free( class );

   system("pause");
   exit( EXIT_SUCCESS );
}

 

 

 

Αφιερωμένος στον... defacer (και ιδιαίτερα τα macros string_() kai int_() :lol: :lol:, τα οποία btw επίτηδες τα έχω με πεζά, αντί για τα conventional κεφαλαία, γιατί με κεφαλαία και αυτά ο συγκεκριμένος κώδικας γινόταν αρκετα πιο... δυσανάγνωστος :lol:).

 

Πέρα από την πλάκα, απλώς τον έγραψα τώρα στο πόδι περισσότερο σε ανάμνηση παλιών καλών (?) εποχών :)

 

ΥΓ. Το γενικότερο concept όμως χρησιμοποείται ακόμα.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Κοινώς (αφήνω έξω την ουσιαστική μηχανολογία του πράγματος που ανέφερε π.χ. ο defacer, και στέκομαι λίγο θεωρητικά αν μιλώντας για binary αρχείο μιλάμε αυτόματα και αυτονόητα για binary εγραφή των δεδομένων όπως τα έχεις στο πρόγραμμα -> στο αρχείο (δηλαδή για τον int γράφεις τα όσα byte σου υπαγορεύει το σύστημα στο αρχείο με το περιεχόμενο του int)

 

Αυτό είναι και το προφανές, όπως και αυτό που διδάσκουν σε όλες τις σχολές κλπ. Όμως αν κάνεις κάτι τέτοιο μπορεί να το μετανιώσεις γιατί στην ουσία το πρόγραμμά σου θα δουλεύει εγγυημένα μόνο με saved αρχεία που παρήχθησαν από την ίδια version του ίδιου compiler με τα ίδια settings στην ίδια πλατφόρμα. Κι αυτό γιατί μια μετέπειτα version του compiler (ή με άλλα settings ή whatever, καταλαβαίνετε) μπορεί να έχει διαφορετικά μεγέθη στα primitives που βρίσκονται μέσα στο struct (το standard δίνει μόνο κάποια "τουλάχιστον τόσο μεγάλο" όρια), ή μπορεί να αλλάξει το padding ανάμεσα στα πεδία, ή μπορεί (λέμε τώρα) να αλλάξει το endianness του συστήματος στο οποίο κάνεις compile.

 

Οπότε, καλά όλα αυτά με τα "τυφλά" writes και reads, αλλά στην πραγματική ζωή απαγορεύεται δια ροπάλου και γενικά θα πρέπει πάντα να κάνεις κάποιο normalization στα δεδομένα που γράφεις για να είσαι σίγουρος πως αυτό που πάει στο αρχείο είναι τελείως ανεξάρτητο από compiler/σύστημα κλπ.

 

Το ίδιο ακριβώς πράγμα γίνεται και όταν μιλάς μέσω socket connections όπου επειδή οι παραπάνω παράμετροι δεν πρόκειται να ταιριάζουν ανάμεσα στα 2 μέρη που επικοινωνούν και πάλι πρέπει να γίνει τέτοια κανονικοποίηση -- βλέπε htonl κλπ.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

 

>
/* ------------------------------------------------------- */
bool student_generate( Student *student )
{
   memset( string_( recbuf[sTUD_FNAME] ), 0, FNAME_MAXLEN );
   for (int i=0; i < FNAME_MAXLEN-1; i++ ) {
--->    char c = 'A' + rand() % 27;
       memcpy( string_( recbuf[sTUD_FNAME+i] ), &c, sizeof(char) );
   }

--->int temp = 18 + rand() % 11;
}

 

Είναι κάτι που είδα πριν καιρό και ήθελα να το αναφέρω στο νήμα των ερωτήσεων για C αλλά όλο ξεχνάω να το κάνω. Τώρα που μου το θύμησες, ας το αναφέρω εδώ και αν κριθεί σκόπιμο το μεταφέρουμε και εκεί.

 

Μια και από την C99 και πέρα επιτρέπονται δηλώσεις μεταβλητών μετά από εκτελέσιμο κώδικα, μια καλή πρακτική που προτείνουν πολλοί είναι να δηλώνεται η μεταβλητή εκεί που θα χρησιμοποιηθεί ώστε να έχει όσο το δυνατόν μικρότερο scope.

 

Στο πρόγραμμα iptables που χρησιμοποιείται για τον ορισμό κανόνων του firewall στο linux βρήκαν ένα bug στον gcc το οποίο κάνει πολύ ωραία πράγματα :) Η συμπεριφορά του iptables-restore διορθώθηκε με αυτό το commit.

 

Για να μην μπλέκουμε με τον κώδικα του iptables, ένα παληκάρι που παρακολουθούσε το νήμα έγραψε ένα πιο κατανοητό κώδικα που παρουσιάζει το bug.

 

>
int i = 0;

for (; {
   char x[5];

   x[i] = '0' + i;
   if (++i == 4) {
       x[i] = '\0'; /* terminate string with null byte */
       printf("%s\n", x);
       break;
   }
}

 

Αυτό που "διαβάζει" κάποιος στον παραπάνω κώδικα είναι ότι το x στο τέλος θα είναι "0123". Ο Clang καθώς και ο gcc πριν την έκδοση 4.7 συμπεριφέρεται έτσι. Στην έκδοση 4.7 όμως, ο gcc έγινε πιο αποδοτικός χρησιμοποιώντας πιο "aggressive memory management" και έτσι αφαιρεί το τμήμα του κώδικα για i = 0,1,2 και στο τέλος το x περιέχει τρία σκουπίδια και τελειώνει με 3 και \0 όπως πριν. Σούπερ αποδοτικός :P

 

Αυτή η συμπεριφορά υφίσταται και στον 4.7.1 που χρησιμοποιώ και ρίχνοντας μια πολύ γρήγορη ματιά στις αλλαγές του 4.7.2 υποθέτω θα υφίσταται και εκεί το bug. Ο 4.8.0 πιστεύω δεν θα έχει το bug λόγω ότι έχει ξαναγραφτεί σε C++ (αν και είπανε ότι η συμπεριφορά θα είναι 1:1 ολόιδια :P).

 

Αν μετά το "x = " βάλουμε ένα printf που να εμφανίζει το x ή το i ή οτιδήποτε, τότε αναγκάζουμε τον optimizer να βγάλει σωστό κώδικα. Οπουδήποτε αλλού εισάγουμε κάτι (όπως πχ μετά το "char x[5]") τότε και πάλι έχουμε την απαλοιφή του i = 0,1,2 τμήματος.

 

Η σίγουρη λύση που εξαφανίζει το πρόβλημα είναι να μετακινήσουμε τη δήλωση του x στην αρχή που είναι και το i. Εμπρός καλή μου ANSI-C δηλαδή :P

  • Like 2
Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@imitheos

 

Πολύ ωραίο, αλλά γιατί είναι bug στον gcc?

 

Σε ANSI C ο κώδικας που κάνει repro δεν κάνει compile, επομένως κάποιος τον έγραψε έχοντας υπόψη ότι μιλάμε για C99 (αυτό το λέω γιατί αν δεν ήταν έτσι τότε πάλι δε θα μιλούσα για bug, θα μιλούσα όμως για εγκληματική αμέλεια όπου προσθέτουμε ένα feature στη γλώσσα και αυτό έχει σαν αποτέλεσμα κώδικας που πριν λειτουργούσε "σωστά" τώρα να λειτουργεί σωστά σύμφωνα με το standard αλλά διαφορετικά απ' ότι πριν).

 

Σε C99 αν δεν κάνω λάθος (διορθώστε με), κανείς δεν σου εγγυάται ότι το x σε κάθε iteration του loop θα δείχνει στην ίδια μνήμη, επομένως τεχνικά σε κάθε iteration παίρνεις 5 bytes junk. O optimizer βλέπει ότι στα 3 πρώτα iterations δεν κάνεις τίποτα με observable side effects και αποφασίζει να μην τα εκτελέσει καν. Επομένως το μόνο bug που υπάρχει είναι στον κώδικα που βλέπουμε παραπάνω.

 

Σωστά;

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

@moukoublen:

 

Μπορείς να δοκιμάσεις το timing και με την clock(), κάπως έτσι....

 

 

 

>
/* ------------------------------------------------------- */
bool write_text( const char *buf )
{
   FILE *fp  = NULL;

   if ( !buf )
       return false;

   if ( NULL == (fp=fopen("_text.dat", "w")) )
       return false;

   size_t len = strlen(buf);
   printf( "writing %d nums in text mode... ", MAX_NUMS );
// ******** START TIMING HERE
   clock_t tstart = clock();
   int temp = fwrite(buf, sizeof(char), len, fp);
   clock_t tend = clock();
// ******** END TIMING HERE
   printf("CPU time elapsed: %f\n", ((double)tend - tstart) / CLOCKS_PER_SEC);
   if ( len != (size_t)temp ) {
       fclose( fp );
       puts( "*** ERROR: fwrite() failed" );
       return false;
   }

   fclose(fp);

   return true;
}

/* ------------------------------------------------------- */
bool write_bin( const int *buf )
{
   FILE *fp = NULL;

   if ( !buf )
       return false;

   if ( NULL == (fp=fopen("_bin.dat", "wb")) )
       return false;

   printf( "writing %d nums in bin  mode...", MAX_NUMS );
// ******** START TIMING HERE
   clock_t tstart = clock();
   int temp = fwrite(buf, sizeof(int), MAX_NUMS, fp);
   clock_t tend = clock();
// ******** END TIMING HERE
   printf("CPU time elapsed: %f\n", ((double)tend - tstart) / CLOCKS_PER_SEC);
   if ( MAX_NUMS != temp ) {
       fclose( fp );
       puts( "*** ERROR: fwrite() failed" );
       return false;
   }

   fclose(fp);

   return true;
}

 

 

 

Σε Win7-64bit με Pelles-C 7.0 64bit, σε Dell i3 μου δίνει:

 

>
RAND_MAX = 1073741823
writing 10000000 nums in bin  mode... CPU time elapsed: 0.274000
writing 10000000 nums in text mode... CPU time elapsed: 0.730000
Πιέστε ένα πλήκτρο για συνέχεια. . .

 

Γενικώς υπάρχει θέμα με τα timings, είτε δεν θα είναι αξιόπιστα είτε θα είναι αλλά όχι cross-platform.

 

@defacer:

 

Έτσι είναι, in real life γίνεται πάντα κάποιο normalization. Κυρίως για το endianess όμως, μιας και το θέμα του εύρους έχει λυθεί με τα fixed-length data types της C99 (προφανώς όπου δεν υποστηρίζεται θέλει κι αυτό normalize). Τα fixed-length datatypes της C99 είναι πραγματική ευλογία!

 

@imitheos:

 

Πολύ ενδιαφέρουσα και χρήσιμη η πληροφορία για το bug του gcc. Δεν το γνώριζα. Γενικώς τα optimizations κάνουν διάφορες "παπαδιές", έχω δει να κάνουν διάφορα τρελά στο παρελθόν... δεν τα θυμάμαι απέξω, αλλά το δικό μου συμπέρασμα είναι πως το pre-mature optimization δεν είναι και τόσο "evil" όσο το παρουσιάζουν οι περισσότερες πηγές. Κατά περιπτώσεις μην σου πω πως είναι και cross-platform safeguard.

 

@imitheos

 

Πολύ ωραίο, αλλά γιατί είναι bug στον gcc?

 

Σε ANSI C ο κώδικας που κάνει repro δεν κάνει compile, επομένως κάποιος τον έγραψε έχοντας υπόψη ότι μιλάμε για C99 (αυτό το λέω γιατί αν δεν ήταν έτσι τότε πάλι δε θα μιλούσα για bug, θα μιλούσα όμως για εγκληματική αμέλεια όπου προσθέτουμε ένα feature στη γλώσσα και αυτό έχει σαν αποτέλεσμα κώδικας που πριν λειτουργούσε "σωστά" τώρα να λειτουργεί σωστά σύμφωνα με το standard αλλά διαφορετικά απ' ότι πριν).

 

Σε C99 αν δεν κάνω λάθος (διορθώστε με), κανείς δεν σου εγγυάται ότι το x σε κάθε iteration του loop θα δείχνει στην ίδια μνήμη, επομένως τεχνικά σε κάθε iteration παίρνεις 5 bytes junk. O optimizer βλέπει ότι στα 3 πρώτα iterations δεν κάνεις τίποτα με observable side effects και αποφασίζει να μην τα εκτελέσει καν. Επομένως το μόνο bug που υπάρχει είναι στον κώδικα που βλέπουμε παραπάνω.

 

Σωστά;

 

Είναι παράλογο να αλλάζει απροειδοποίητα ο compiler τη θέση μνήμης μιας μεταβλητή που έχει γίνει defined explicitly. Δεν γνωρίζω να υπάρχει κάτι τέτοιο τεκμηριωμένο στο C99, θα μου έκανε όμως τρομερή εντύπωση αν υπάρχει.

 

Οπότε μιλάμε καθαρά για bug του compiler, μιας και ο κώδικας (από όσο γνωρίζω δηλαδή) δείχνει να κάνει μια χαρά conform στα τεκμηριωμένα στάνταρ της γλώσσας.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Είναι παράλογο να αλλάζει απροειδοποίητα ο compiler τη θέση μνήμης μιας μεταβλητή που έχει γίνει defined explicitly. Δεν γνωρίζω να υπάρχει κάτι τέτοιο τεκμηριωμένο στο C99, θα μου έκανε όμως τρομερή εντύπωση αν υπάρχει.

 

Οπότε μιλάμε καθαρά για bug του compiler, μιας και ο κώδικας (από όσο γνωρίζω δηλαδή) δείχνει να κάνει μια χαρά conform στα τεκμηριωμένα στάνταρ της γλώσσας.

 

Παράλογο είναι να λέει κάτι το standard και κάποιος να υποστηρίζει κάτι διαφορετικό, ή το να μη γνωρίζουμε τι ακριβώς λέει το standard αλλά να έχουμε δική μας άποψη. Επιπλέον αυτό που έγραψες αντιφάσκει: "Δεν γνωρίζω να υπάρχει κάτι τέτοιο τεκμηριωμένο..." vs "δείχνει να κάνει μια χαρά conform στα τεκμηριωμένα". Μήπως ήθελες να πεις "γνωρίζω ότι δεν υπάρχει";

 

Δε θα επεκταθώ περισσότερο.

Συνδέστε για να σχολιάσετε
Κοινοποίηση σε άλλες σελίδες

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα

  • Δημιουργία νέου...