ΠΕΡΙΕΧΟΜΕΝΑ
I. ΤΟ BUFFERED I/O SYSTEM (ΣΥΣΤΗΜΑ ΠΡΟΣΒΑΣΗΣ ΥΨΗΛΟΥ
ΕΠΙΠΕΔΟΥ)
6. ΟΙ ΣΥΝΑΡΤΗΣΕΙΣ ferror()
ΚΑΙ rewind()
8.
ΟΙ ΣΥΝΑΡΤΗΣΕΙΣ getw() ΚΑΙ putw()
9.
ΟΙ ΣΥΝΑΡΤΗΣΕΙΣ fgets() ΚΑΙ fputs()
10.
ΟΙ ΣΥΝΑΡΤΗΣΕΙΣ fread() ΚΑΙ fwrite()
11.
Η ΣΥΝΑΡΤΗΣΗ fseek()
ΚΑΙ Η ΤΥΧΑΙΑ ΠΡΟΣΠΕΛΑΣΗ
13. ΟΙ ΣΥΝΑΡΤΗΣΕΙΣ fprintf() ΚΑΙ fscanf()
14. ΔΙΑΓΡΑΦΗ ΑΡΧΕΙΩΝ
II. TO UNBUFFERED
(UNIX LIKE) I/O SYSTEM (ΣΥΣΤΗΜΑ ΠΡΟΣΒΑΣΗΣ
ΧΑΜΗΛΟΥ ΕΠΙΠΕΔΟΥ)
1. ΟΙ ΣΥΝΑΡΤΗΣΕΙΣ open(),
create() ΚΑΙ close()
2.
ΟΙ ΣΥΝΑΡΤΗΣΕΙΣ read() ΚΑΙ write()
4. ΧΑΜΗΛΟΥ ΕΠΙΠΕΔΟΥ ΤΥΧΑΙΑ ΠΡΟΣΠΕΛΑΣΗ
III. ΕΠΙΛΕΓΟΝΤΑΣ ΣΥΣΤΗΜΑ
ΚΕΦΑΛΑΙΟ 9ο
Στην C,
αρχείο (file) είναι
μία λογική ιδέα, η οποία μπορεί να αφορά οτιδήποτε, από αρχεία δίσκου μέχρι
τερματικά. Για παράδειγμα, μία ροή συνεργάζεται με ένα συγκεκριμένο αρχείο,
εκτελώντας μία διαδικασία ανοίγματος (open). Από τη
στιγμή που το αρχείο θα ανοίξει, πληροφορίες μπορεί να ανταλλάσσονται μεταξύ
του αρχείου και του προγράμματος.
Πάντως,
δεν έχουν όλα τα αρχεία τις ίδιες δυνατότητες. Για παράδειγμα, ένα αρχείο
μπορεί να υποστηρίξει τυχαία προσπέλαση, ενώ ένα τερματικό όχι. Αυτό εξηγεί ένα
σοβαρό σημείο στο Ι/Ο σύστημα της C. Όλα τα streams είναι ίδια, ενώ τα αρχεία όχι. Αν το αρχείο μπορεί να
υποστηρίξει random access, το
άνοιγμα αυτού του αρχείου θα τοποθετήσει τον file-position indicator στην αρχή
του αρχείου. Καθώς ένας χαρακτήρας διαβάζεται από (ή γράφεται στο) αρχείο, η
τιμή του file position indicator αυξάνεται
συνεχίζοντας τη διαδικασία μέσα στο αρχείο. Από τη στιγμή που το αρχείο θα
κλείσει, παύει και η συνεργασία μεταξύ αρχείου και ροής. Σε ροές, οι οποίες
έχουν ανοιχτεί για έξοδο, το κλείσιμο τους προκαλεί τη μεταφορά όλων των
δεδομένων του buffer στην
εξωτερική συσκευή. Η διαδικασία αυτή καλείται flush (καθάρισμα) της ροής και εγγυάται ότι δεν πρόκειται να
χαθούν πληροφορίες, παραμένοντας στον buffer.
Όλα τα
αρχεία κλείνουν αυτόματα, με τον ομαλό τερματισμό του προγράμματος και την
επιστροφή της main( ) στο
σύστημα, ή την κλήση της συνάρτησης exit( ). Τα
αρχεία δεν κλείνουν με την κλήση της
συνάρτησης abort( ), ή
φυσικά με πιθανή κατάρρευση του προγράμματος (crash).
Με την
έναρξη της εκτέλεσης ενός προγράμματος ανοίγουν τρεις προκαθορισμένες ροές.
Αυτές είναι η stdin, stdout και η stderr και
αναφέρονται στις τυπικές I/O συσκευές που είναι συνδεδεμένες στο σύστημα. Οι
εκδόσεις 1.5 και 2.0 της Turbo C ανοίγουν
επίσης τις stdprn (standard printer) και stdaux (standard
auxilary device). Για τα περισσότερα συστήματα η stdaux είναι η κονσόλα. Υπενθυμίζεται ότι τα περισσότερα
λειτουργικά συστήματα (συμπεριλαμβανομένου και του DOS) επιτρέπουν την ανακατεύθυνση των Ι/Ο λειτουργιών (I/O
redirection), με αποτέλεσμα διαδικασίες οι οποίες διαβάζουν ή γράφουν
σ’ αυτές τις ροές να αναφέρονται σε άλλες συσκευές (η ανακατεύθυνση των Ι/Ο
λειτουργιών είναι μία διαδικασία κατά την οποία οι πληροφορίες που
κατευθύνονται σε μία συσκευή, τελικά καταλήγουν σε άλλη). Φυσικά, δεν πρέπει ΣΕ
ΚΑΜΙΑ ΠΕΡΙΠΤΩΣΗ να προσπαθήσουμε να κλείσουμε αυτές τις ροές.
Κάθε ροή η
οποία σχετίζεται με κάποιο αρχείο διαθέτει μία δομή ελέγχου του τύπου FILE. Η δομή αυτή είναι δηλωμένη στο header file <stdio.h>.
Φυσικά, δεν πρέπει να επιχειρηθεί καμία μορφή τροποποίησης αυτής της δομής.
Στο βαθμό
που ο προγραμματιστής ασχολείται, όλες οι Ι/Ο διαδικασίες λαμβάνουν χώρα μέσω
ροών, οι οποίες είναι αλληλουχίες από χαρακτήρες (bytes). Όλες οι ροές είναι ίδιες. Το σύστημα συνδέει τη ροή με
ένα αρχείο, το οποίο μπορεί να είναι οποιαδήποτε εξωτερική συσκευή. Επειδή
διαφορετικές συσκευές έχουν διαφορετικές δυνατότητες, όλα τα αρχεία δεν είναι
ίδια. Φυσικά οι διαφορές αυτές ελαχιστοποιούνται από το Ι/Ο σύστημα της C, το
οποίο τροποποιεί τις πληροφορίες που έρχονται από τη συσκευή σε μία ροή και
αντίστροφα. Εκτός των περιορισμών, ότι μόνο συγκεκριμένοι τύποι αρχείων
υποστηρίζουν τυχαία προσπέλαση, ο προγραμματιστής δεν έχει ανάγκη να ανησυχεί
για να την ενεργή φυσική συσκευή και είναι ελεύθερος να συγκεντρωθεί στη λογική
συσκευή τη ροή.
Αν αυτή η
προσέγγιση φαίνεται παράξενη και μπερδεμένη, ας κοιτάξουμε σε γλώσσες όπως η PASCAL, FORTRAN, και COBOL, όπου κάθε συσκευή υποστηρίζεται από εντελώς
ξεχωριστό σύστημα αρχείων. Η προσέγγιση της C απαιτεί από τον προγραμματιστή να
σκεφτεί μόνο τους όρους των ροών και να χρησιμοποιήσει μόνο ένα σύστημα αρχείων
για να εκτελέσει όλες τις Ι/Ο λειτουργίες.
I. TO BUFFERED I/O SYSTEM (ΣΥΣΤΗΜΑ ΠΡΟΣΒΑΣΗΣ ΥΨΗΛΟΥ ΕΠΙΠΕΔΟΥ)
Πρόκειται
για το υψηλού επιπέδου σύστημα διαχείρισης αρχείων το οποίο περιλαμβάνει
αρκετές συναρτήσεις. Όλες αυτές οι συναρτήσεις απαιτούν τη δήλωση του header file <stdio.h>.
Το νήμα το
οποίο δένει όλα τα στοιχεία του buffered I/O System είναι ο file pointer. O file
pointer είναι ένας δείκτης ο οποίος καθορίζει διάφορα πράγματα γύρω
από το αρχείο, συμπεριλαμβάνοντας το όνομα, την κατάσταση, και την τρέχουσα
θέση σ’ αυτό. Στην πραγματικότητα, ο file
pointer αναγνωρίζει ένα συγκεκριμένο αρχείο δίσκου και
χρησιμοποιείται από τη ροή που συσχετίζεται με αυτό, έτσι ώστε να πει σε κάθε
μία από τις συναρτήσεις που να εκτελέσει την αντίστοιχη εργασία. Ένας file pointer είναι ένας δείκτης του τύπου FILE ο οποίος ορίζεται στο stdio.h.
Η
συνάρτηση fopen( )
ανοίγει μία ροή προς χρήση και τη συνδέει με κάποιο αρχείο. Στις περισσότερες
περιπτώσεις το αρχείο αυτό είναι ένα αρχείο δίσκου. Η συνάρτηση βρίσκεται στο
<stdio.h> και το πρωτότυπό της είναι:
FILE *fopen(char *filename, *mode);
όπου mode είναι το string το οποίο
περιέχει τον επιθυμητό κωδικό ανοίγματος. Το filename πρέπει να είναι ένα string από χαρακτήρες το οποίο περιέχει ένα αποδεκτό από το
λειτουργικό σύστημα όνομα αρχείου (φυσικό όνομα στο δίσκο) και ενδέχεται να
περιέχει και τη διαδρομή (path) στην
οποία αυτό βρίσκεται.
Οι
αποδεκτές τιμές για το mode
string είναι:
“r” Open a text file for reading
“w” Create a text file for writing
“a” Append to a text file
“rb” Open a binary file for writing
“wb” Create a binary file for writing
“ab” Append to a binary file
“r+” Open a text file for read / write
“w+” Create a text file for read / write
“a+” Open
or Create a text file for read / write
“r+b” Open
a binary file for read / write
“w+b” Create
a binary file for read / write
“a+b” Open
or Create a binary file for read / write
“rt” Open
a text file for reading
“wt” Create
a text file for writing
“at” Append
to a text file
“r+t” Open
a text file for read / write
“a+t” Open
or Create a text file for read / write
Στην
περίπτωση των text αρχείων
χαρακτήρες όπως το carriage return ή το line feed μεταφράζονται σε newline characters κατά την είσοδο. Κατά την έξοδο συμβαίνει
το αντίστροφο. Καμία μετάφραση αυτού του είδους δε συμβαίνει στα binary files. Έτσι αν επιθυμούμε να ανοίξουμε
ένα αρχείο για γράψιμο με το όνομα test,
γράφουμε:
fp = fopen(“test”, “w”);
όπου fp είναι μία μεταβλητή του τύπου FILE *. Παρ’ όλα ταύτα τις περισσότερες φορές γράφουμε:
if ( (fp = fopen(“test”, “w”) ) ==NULL) {
puts(“Cannot
Open File \ n”);
exit(1);
}
Η μέθοδος
αυτή ανιχνεύει όλες τις περιπτώσεις λάθους κατά τη διαδικασία ανοίγματος ενός
αρχείου, όπως write protected ή full disk, πριν επιχειρήσει την εγγραφή σε
αυτό. Ένα NULL (0X00)
χρησιμοποιείται επειδή ο file
pointer ποτέ δεν πήρε τιμή. Το NULL είναι ένα macro δηλωμένο
στο stdio.h.
Αν γίνει
χρήση της fopen( ) για
γράψιμο και το αρχείο προϋπάρχει, όλες οι εγγραφές σε αυτό θα χαθούν και θα
αρχίσει ένα νέο αρχείο. Αν δεν υπάρχει το αρχείο, θα δημιουργηθεί από την αρχή.
Αν θέλουμε να προσθέσουμε εγγραφές σε ένα αρχείο θα πρέπει να κάνουμε χρήση της
επιλογής “a”. Αν το
αρχείο δεν υπάρχει δημιουργείται ένα καινούριο.
Το άνοιγμα
ενός αρχείου για διάβασμα, προϋποθέτει την ύπαρξη του αρχείου στο δίσκο. Αν
αυτό δεν υπάρχει, ένα λάθος θα επιστραφεί.
Η putc( ) χρησιμοποιείται για να γράφει χαρακτήρες σε μία
ροή η οποία προηγουμένως ανοίχτηκε για γράψιμο με χρήση της συνάρτησης fopen( ). Η συνάρτηση είναι δηλωμένη ως εξής:
int putc(int ch, FILE *fp);
όπου fp είναι ο file
pointer ο οποίος επιστρέφεται από την fopen( ) και ch είναι ο
προς εγγραφή χαρακτήρας. Ο file
pointer διευκρινίζει στην putc( ) σε
ποιο αρχείο δίσκου θα γράψει. Για ιστορικούς λόγους ο ch καλείται int, αλλά
μόνο το low-order byte χρησιμοποιείται.
Η putc( ) επιστρέφει τον χαρακτήρα που έγραψε σε περίπτωση
επιτυχίας, ενώ επιστρέφει EOF σε περίπτωση αποτυχίας. Το EOF είναι ένα macro δηλωμένο
στο stdio.h και αντιπροσωπεύει τον χαρακτήρα που μαρκάρει το τέλος του
αρχείου (^Ζ).
Χρησιμοποιείται
για την ανάγνωση χαρακτήρων από ένα stream το οποίο
έχει ανοιχτεί σε real mode με την fopen( ). Η συνάρτηση έχει δηλωθεί στο stdio.h ως εξής:
int getc(FILE *fp);
όπου fp είναι ένας file
pointer του τύπου FILE όπου
επιστρέφεται από την fopen( ). Για
ιστορικούς λόγους η getc( )
επιστρέφει int, αλλά το high order byte είναι 0. Η getc( )
επιστρέφει EOF όταν
ανιχνευθεί το τέλος του αρχείου. Για να διαβάσουμε ένα text file ως το τέλος θα πρέπει να γράψουμε:
ch = getc (fp);
while
(ch! =EOF) {
ch = getc(fp);
}
Όπως ήδη
αναφέρθηκε το buffered file sytstem, μπορεί
επίσης να διαχειριστεί και δυαδικά δεδομένα. Όταν ένα αρχείο ανοίγεται για
δυαδική είσοδο, ενδέχεται να διαβαστεί μία τιμή ίση με EOF. Αυτό θα αναγκάσει την προηγούμενη ρουτίνα να τερματιστεί
πρόωρα. Η Turbo C, για να
λύσει αυτό το πρόβλημα, περιλαμβάνει τη συνάρτηση feof( ), η οποία χρησιμοποιείται για την ανίχνευση του τέλους
των δυαδικών αρχείων. Η feof( )
δέχεται ένα file pointer σαν
όρισμα και επιστρέφει 1 αν ανιχνευτεί το τέλος του αρχείου, ενώ επιστρέφει 0 σε
αντίθετη περίπτωση. Έτσι, η παρακάτω ρουτίνα διαβάζει ένα δυαδικό αρχείο έως
ότου βρει το τέλος του:
while (!feof(fp) ) ch = getc(fp);
Φυσικά η
μέθοδος αυτή μπορεί να εφαρμοστεί και σε αρχεία κειμένου με την ίδια επιτυχία.
Κλείνει μία
ροή η οποία έχει ανοιχτεί με κλήση της fopen( ). Όλες οι ροές πρέπει να κλείνουν πριν το τέλος του
προγράμματος. Η διαδικασία αυτή γράφει όλα τα δεδομένα που έχουν μείνει στον buffer και επιτελεί ένα τυπικό κλείσιμο (επιπέδου
λειτουργικού) στο αρχείο. Αποτυχία στο κλείσιμο μιας ροής ενέχει των ειδών τα
προβλήματα στο πρόγραμμα. Η κλήση της fclose ( ),
επίσης ελευθερώνει το χώρο μνήμης που καταλάμβανε η ροή. Φυσικά υπάρχει κάποιος
περιορισμός από το λειτουργικό σύστημα για τον αριθμό ανοιχτών αρχείων, αλλά
πάντως είναι καλό να κλείνουμε το ένα αρχείο πριν ανοίξουμε άλλο. Η fclose( ) έχει δηλωθεί ως εξής:
int fclose(FILE *fp);
όπου fp είναι ο file
pointer που επιστρέφει η κλήση της fopen( ). Αν επιστραφεί 0, η διαδικασία κλεισίματος ήταν
επιτυχής, σε οποιαδήποτε άλλη περίπτωση έχουμε λάθος. Μπορούμε να κάνουμε χρήση
της ferror( ) για τον εντοπισμό και
την αναφορά πιθανών λαθών. Βασικά, η fclose( )
αποτυγχάνει όταν η δισκέτα έχει μετακινηθεί ή δεν υπάρχει άλλος χώρος σ’ αυτή.
vi)
Οι
Συναρτήσεις ferror( ) και rewind( )
Η
συνάρτηση ferror( )
χρησιμοποιείται για την ανίχνευση λαθών στα αρχεία. Η δήλωσή της είναι:
int ferror(FILE *fp);
όπου fp είναι οποιοσδήποτε αποδεκτός file pointer. Επιστρέφει True αν προέκυψε κάποιο λάθος στην προηγούμενη διαδικασία,
αλλιώς επιστρέφει False. Επειδή
κάθε διαδικασία στα αρχεία επηρεάζει την ferror( ), αυτή θα πρέπει να καλείται αμέσως μετά από κάθε πράξη,
αλλιώς το λάθος μπορεί να χαθεί.
Η
συνάρτηση rewind( ),
αρχικοποιεί τον file position locator στην αρχή
του αρχείου που βρίσκεται στο όρισμά της. Η δήλωση είναι:
void rewind (FILE *fp);
όπου fp είναι ένας file
pointer.
Το
παρακάτω πρόγραμμα δείχνει τη χρήση των συναρτήσεων που προαναφέρθηκαν. Απλά
διαβάζει χαρακτήρες από το πληκτρολόγιο και τους γράφει σε ένα δίσκο έως ότου
πληκτρολογηθεί ένα $. Το όνομα του αρχείου ορίζεται από το command line. Έτσι αν ονομάσουμε αυτό το
πρόγραμμα test.exe, και πληκτρολογήσουμε test file, τότε αυτό μας επιτρέπει να κάνουμε εγγραφές στο αρχείο το
οποίο θα καλείται file. Π.χ.:
#include <stdio.h>
main(argc, argv)
int argc;
char *argv [ ] ;
{
FILE *fp;
char ch;
if
(argc!=2) {
printf(“You
Forgot To Enter The Filename \n”);
exit
(1);
}
do {
ch
= getchar( );
putc
(ch, fp);
} while (ch! = ‘ $ ’);
fclose(fp);
}
Το
παρακάτω πρόγραμμα διαβάζει οποιοδήποτε ASCII αρχείο και εμφανίζει τα περιεχόμενά του στην οθόνη.
#include <stdio.h>
main (argc, argv)
int argc;
char *argv [ ]
;
{
FILE *fp;
char ch;
if
(argc!=2) {
printf(“You
Forgot To Enter The Filename \n”);
exit(1);
}
if (
(fp=fopen (argv [1], “r”) )==NULL) {
printf(“Cannot
Open File \n”);
exit(1);
}
ch=getc(fp); / *
Διάβασμα χαρακτήρα * /
while (ch! = EOF {
putchar (ch); / * Έξοδος Στην Οθόνη */
ch=getc(fp);
}
fclost
(fp);
}
Το
παρακάτω πρόγραμμα αντιγράφει ένα αρχείο οποιουδήποτε τύπου. Υπενθυμίζεται ότι
τα αρχεία ανοίγουν σε δυαδική μορφή και η συνάρτηση feof( ) χρησιμοποιείται για τον έλεγχο τέλους του αρχείου.
#include <stdio.h>
main (argc, argv)
int argc;
char *argv[ ] ;
{
FILE *in, *out;
char ch;
if
(argc!=3) {
printf(“You
Forgot To Enter The Filename \n”);
exit(
1);
}
if ( (in
= fopen (argv [1], “rb”) ) ==NULL ) {
printf
(“Cannot Open Source File \n”);
exit(1);
}
if ( (
out = fopen (argv [2], “wb”) )==NULL )
{
printf(“Cannot
Open Destination File \n”);
exit
(1);
}
while
(!feof(in) ) {
ch
= getc (in);
if
(!feof (in) putc (ch, out);
}
fclose
(in);
fclose
(out);
}
viii)
Οι συναρτήσεις
getw( ) και putw( )
Αποτελούν
συμπλήρωμα των getc( ) και putc( ). Χρησιμοποιούνται για διάβασμα και γράψιμο
ακεραίων από και προς τον δίσκο. Η χρήση τους είναι ακριβώς ίδια με των getc( ) και putc( ), με
μόνη διαφορά ότι για απλούς χαρακτήρες (bytes) χρησιμοποιούνται με ακεραίους (δύο bytes – words). Π.χ.:
putw(100, fp);
Η Turbo C διαθέτει επιπλέον συναρτήσεις οι
οποίες γράφουν και διαβάζουν strings σε ροές.
Αυτές είναι οι fgets( ) και fputs( ). Είναι δηλωμένες:
char *fputs(char *str, FILE *fp);
char *fgets(char *str, int
length, FILE *fp);
Η
συνάρτηση fputs( )
εργάζεται ακριβώς όπως η puts( ) με
μόνη διαφορά ότι γράφει το string στο
συγκεκριμένο stream. Η
συνάρτηση fgets( ) και
διαβάζει ένα string από μία
καθορισμένη ροή, έως ότου διαβαστεί ένας newline character ή length
χαρακτήρες. Αν διαβαστεί ένας newline
character θα αποτελέσει μέρος του string. Πάντως, όταν η fgets( )
τερματιστεί, το εξαγόμενο string θα είναι NULL-terminated.
x)
Οι
Συναρτήσεις fread( ) και fwrite( )
Το Buffered I/O system διαθέτει δύο συναρτήσεις, οι οποίες επιτρέπουν το γράψιμο
και διάβασμα ολόκληρων block
δεδομένων. Αυτές είναι δηλωμένες ως εξής:
int fread(void *buffer, int num_bytes, int count, FILE
*fp);
int fwrite(void *buffer, int num_bytes, int count,
FILE *fp);
Στην
περίπτωση της fread( ), ο buffer είναι ένας pointer σε ένα
τμήμα της μνήμης το οποίο δέχεται δεδομένα από το αρχείο. Για την fwrite( ), ο bufferείναι ένας
pointerσε ένα
τμήμα της μνήμης, το οποίο περιέχει τα δεδομένα τα οποία θα γραφτούν στο δίσκο.
Ο αριθμός bytes που θα διαβαστούν ή θα γραφτούν ορίζεται από τη
μεταβλητή num_bytes. Το όρισμα count
διευκρινίζει πόσα αντικείμενα (μεγέθους num_bytes το
καθένα) θα γραφτούν ή θα διαβαστούν. Τέλος ο fp είναι ο file
pointer με τον οποίο ανοίχτηκε η ροή.
Από τη
στιγμή που το αρχείο ανοίχτηκε για δυαδικά δεδομένα, οι fread( ) και fwrite( )
μπορούν να γραφούν και να διαβάζουν οποιοδήποτε τύπο πληροφοριών. Π.χ. το
παρακάτω πρόγραμμα γράφει ένα float
σε ένα αρχείο δίσκου.
#include <stdio.h>
main( )
{
FILE *fp;
float
f=12.23;
if ( (fp=fopen(“test”, “wb”) ) == NULL) {
printf(“Cannot
Open File \n”);
return;
}
fwrite
(&f. sizeof (float), 1, fp);
fclose(fp);
}
Όπως δείχνει
το παραπάνω πρόγραμμα, ο buffer συνήθως
είναι μία απλή μεταβλητή. Μία από τις πιο χρήσιμες εργασίες των fread( ) και fwrite( )
αποτελεί το διάβασμα και το γράψιμο σύνθετων μεταβλητών όπως πίνακες και δομές.
Για παράδειγμα, το παρακάτω πρόγραμμα γράφει τον floating point πίνακα balance στο
αρχείο balance
χρησιμοποιώντας μία απλή fwrite( ).
#include <stdio.h>
main( )
{
FILE *fp;
float
balance [100];
if ( (
fp = fopen (“balance”, “wt”) ) == NULL)
{
printf(“Cannot
Open File \n”);
return;
}
.
.
.
fwrite(balance,
sizeof (balance),1 ,fp);
.
.
.
fclose(fp);
}
xi)
H Συνάρτηση
fseek( ) και η
τυχαία προσπέλαση
Στην C
μπορούμε να εκτελέσουμε τυχαίες διαδικασίες διαβάσματος και εγγραφής
χρησιμοποιώντας το buffered I/O system με τη βοήθεια της
συνάρτησης fseek( ) η
οποία θέτει τον file position locator σε ένα
συγκεκριμένο σημείο. Η δήλωση της συνάρτησης είναι:
int fseek (FILE
*fp, long int num_bytes, int origin);
όπου p είναι ένας file
pointer ο οποίος επιστρέφεται από την κλήση της fopen( ), num_bytes είναι ένας long ακέραιος που αντιπροσωπεύει την τρέχουσα θέση και origin ένα από τα παρακάτω macros:
SEEK_SET Αρχή Του Αρχείου 0
SEEK_CUR Τρέχουσα Θέση 1
SEEK_END Τέλος Του Αρχείου 2
Για να αναζητήσουμε
num_bytes από την αρχή του αρχείου θα πρέπει να χρησιμοποιήσουμε το SEEK_SET.
Για να
αναζητήσουμε από την τρέχουσα θέση, θα πρέπει να χρησιμοποιήσουμε το SEEK_CUR.
Τέλος, για
να αναζητήσουμε από το τέλος του αρχείου θα πρέπει να χρησιμοποιήσουμε το SEEK_END.
Υπενθυμίζεται
ότι το offset θα πρέπει να είναι longint για να υποστηρίζει αρχεία
μεγαλύτερα των 64Kb.
Η χρήση
της fseek( ) σε αρχεία κειμένου δεν
ενδείκνυται γιατί η μετάφραση των χαρακτήρων προκαλεί σφάλματα θέσεως (position errors). Η χρήση της seek( ) ενδείκνυται μόνο σε δυαδικά αρχεία.
Π.χ. ο
παρακάτω κώδικας διαβάζει το 234 byte σε ένα
αρχείο που ονομάζεται test :
func1( )
{
FILE *fp;
if (
( fp = fopen (“test”, “rb”) ) == NULL)
{
printf(“Cannot
Open File \n”);
exit
(1);
}
fseek(fp.234L,
0);
return
getc(fp);
}
Παρατηρείται
ότι ο τροποποιητής L
προστίθεται στη σταθερά 234, για να υποδείξει στον compiler ότι η σταθερά αυτή είναι long int. Η χρήση ενός cast θα είχε
το ίδιο αποτέλεσμα. Αντίθετα, η χρήση ενός κανονικού ακεραίου πιθανότατα θα
προκαλούσε σφάλματα όταν θα αναμενόταν ένας long.
Η fseek( ) σε
περίπτωση επιτυχίας επιστρέφει 0. Αντίθετα, μία μη μηδενική τιμή υποδεικνύει
σφάλμα.
Το
παρακάτω παράδειγμα είναι πιο ενδιαφέρον, μια κι εδώ η fseek( ) μας επιτρέπει να εξετάσουμε τα περιεχόμενα ενός αρχείου
και σε ASCII και σε δεκαεξαδική μορφή.
Μας επιτρέπει να διαβάζουμε sectors των 128 bytes. Η έξοδος εμφανίζει στοιχεία παρόμοια με αυτά του Debuger του DOS, όταν του δίνουμε την εντολή D (Dump
Memmory Command). Η έξοδος από το πρόγραμμα γίνεται με την πληκτρολόγηση
–1.
#include <stdio.h>
#include<ctype.h>
#define SIZE 128
char buf [SIZE];
void display( );
main (argc, argv)
int argc;
char *argv[ ];
{
FILE *fp;
int
sector, numread;
if
(argc!=2) {
printf(“Usage : dump
filename \n”);
exit(1);
}
if (
(fp=fopen (argv[1], “rb”) ) ==NULL) {
printf
(“Cannot Open File \n“);
exit
(1);
}
do {
printf(“Enter
Sector :”);
scanf
(“%d”, §or);
if
(sector >= 0) {
if
(fseek (fp, sector*SIZE, SEEK_SET) ) {
printf(“Seek
Error \n”);
}
if
( (numread = fread (buf, 1, SIZE, fp) )! = SIZE
{
printf(“EOF
Reached \n”);
}
display
(numread);
}
} while (sector >= 0);
}
void display (int numread)
{
int
i, j;
for (i=0;
i<numread/16; i++) {
for
( j=0; j<16; j++) printf (“%3X”, buf[ i * 16 +j ] ) ;
printf
(“ “);
for
(j=0; j<16; j++) {
if
(isprintf (buf [i * 16 + j] ) )
printf(“%c”, buf [i * 16 + j ] );
else
printf (“ . “);
}
printf ( “\n”);
}
}
Σημειώνεται
ότι η συνάρτηση βιβλιοθήκης isprint( )
καλείται για να ξεχωρίσει του μη εκτυπώσιμους χαρακτήρες. Επιστρέφει True αν ο χαρακτήρας είναι εκτυπώσιμος και False αν όχι. Η συνάρτηση αυτή βρίσκεται στο ctype.h.
Όταν ένα
πρόγραμμα Turbo C αρχίζει
να εκτελείται, τρεις ροές ανοίγουν αυτόματα. Η standard input, standard
output και standard
error. Η έκδοση 1.5 ανοίγει επίσης τις stdprn και stdaux. Κανονικά
οι ροές αυτές αναφέρονται στην κονσόλα (οθόνη) αλλά μπορούν να αλλαχτούν από το
λειτουργικό σύστημα και να ανακατευθυνθούν (redirection) σε μία άλλη συσκευή. Επειδή αποτελούν file pointers το λειτουργικό σύστημα μπορεί να
τις χρησιμοποιήσει για να πετύχει Ι/Ο διαδικασίες στην οθόνη. Για παράδειγμα η
συνάρτηση putchar( ) θα
μπορούσε να είναι δηλωμένη ως εξής:
putchar ( c )
char c;
{
putc
(c, stdout);
}
Σε γενικές
γραμμές η stdin
χρησιμοποιείται για διάβασμα από την οθόνη ενώ οι stdout και stderr για
γράψιμο σ’ αυτή. Θα μπορούσαμε να χρησιμοποιήσουμε τις ροές αυτές σαν file pointers σε οποιαδήποτε συνάρτηση
χρησιμοποιούσε μία μεταβλητή τύπου FILE *.
Πάντως, θα
πρέπει να θυμόμαστε ότι οι stdin, stdout και stderr δεν είναι
μεταβλητές αλλά σταθερές και ως εκ τούτου δεν πρέπει να αλλαχτούν. Αυτοί οι file pointers δημιουργούνται αυτόματα με την
έναρξη του προγράμματος και κλείνουν αυτόματα με το τέλος του. Δεν πρέπει να
προσπαθήσουμε να τις κλείσουμε εμείς.
xiii)
Οι
Συναρτήσεις fprint( ) και fscanf( )
Οι
συναρτήσεις αυτές συμπεριφέρονται ακριβώς όπως οι printf και scanf με τη
διαφορά ότι απευθύνονται σε αρχεία δίσκων. Οι δηλώσεις τους είναι:
fprintf (fp, “Control String”, Argument List);
fscanf
(fp, “Control String”, Argument List);
όπου fp ένας file
pointer ο οποίος επιστρέφεται από την κλήση μιας fopen( ). Εκτός του ότι ανακατευθύνουν την έξοδό τους σε
ένα αρχείο ορισμένο από το fp, η
αντίδραση και η λειτουργία τους είναι όμοιες με τις scanf( ) και printf( ). To παρακάτω παράδειγμα δημιουργεί έναν τηλεφωνικό
κατάλογο:
#include <stdio.h>
void add_num( ), lookup( );
main( )
{
char
choice;
do {
choice
= menu( );
switch
(choice) {
case
‘a’ : add_num( );
break;
case
‘b’ : lookup( );
break;
}
} while (choice ! = ‘q’);
}
menu / * Εμφάνιση του Menu και Επιλογή * /
{
char
ch;
do {
printf
(“ (A)dd, (L)ookUp, or (Q)uit : “) ;
ch
= tolower (getche( ) );
printf
( “ \ n “);
} while (ch! = ‘q’ && ch! = ‘a’ && ch!= ‘1’);
return
ch;
}
void add_num( )
/ * Προσθήκη Εγγραφής * /
{
FILE *fp;
char name
[80], name2 [80];
int
a_code, exchg, num;
if
(fp = fopen (“phone”, “r”) ) = NULL ) {
printf
(“Cannot Open Directory File \ n “);
exit
(1);
}
printf
(“Name ?”);
gets
(name);
while
(!feof (fp) ) {
fscanf(fp,
“%s%d%d%d”, name2, &a_code, &exchg, &num);
if
(!strcmp (name, name2) ) {
printf
(“%s : (%d) %d - %d \n”, name, a_code, exchg, num);
break;
}
}
fclose (fp);
}
Αν εκτελέσουμε το ανωτέρω παράδειγμα βλέπουμε ότι οι πληροφορίες αποθηκεύονται όπως ακριβώς θα εμφανίζονταν στην οθόνη.
Πάντως αν
και οι συναρτήσεις fprintf( ) και fscanf( ) είναι συχνά ο πιο εύκολος τρόπος να γράψουμε και
να διαβάσουμε δεδομένα, σε και από αρχεία, δεν είναι πάντα και ο πιο
αποτελεσματικός. Επειδή τα φορμαρισμένα ASCIΙ δεδομένα γράφονται όπως ακριβώς και στην οθόνη,
καταλαμβάνουν περισσότερο χώρο και είναι πιο αργά από τα δυαδικά. Αν λοιπόν ο
χώρος και η ταχύτητα μας απασχολούν τότε είναι καλύτερα να χρησιμοποιήσουμε τις
fread( ) και fwrite( ).
Η συνάρτηση
remove( ) σβήνει ένα αρχείο. Η
σύνταξή της είναι η εξής:
int remove (char *filename);
Επιστρέφει 0 σε
επιτυχία, ή μη μηδενική τιμή σε αποτυχία. Π.χ.:
#include <stdio.h>
main (int agrc, char
*argv[ ])
{
if
(remove (argv [1] == -1)
printf
(“Remove Error ! “);
}
II. TO UNBUFFERED (UNIX LIKE) I/O – ΣΥΣΤΗΜΑ (ΣΥΣΤΗΜΑ ΠΡΟΣΒΑΣΗΣ ΧΑΜΗΛΟΥ ΕΠΙΠΕΔΟΥ)
Επειδή
αρχικά η C αναπτύχθηκε κάτω από UNIX,
δημιουργήθηκε ένα δεύτερο σύστημα Ι/Ο σε αρχεία. Χρησιμοποιεί συναρτήσεις
διαφορετικές από το buffered I/O system. Οι συναρτήσεις του low-level UNIX-like Ι/Ο συστήματος, απαιτούν τη δήλωση επικεφαλίδας <io.h>. Το
σύστημα αυτό καλείται και unbuffered
I/O system γιατί δεν
υποστηρίζει buffers τους
οποίους καλείται ο προγραμματιστής να δημιουργήσει και να διαχειριστεί. Το
σύστημα αυτό σχεδόν έχει πάψει να χρησιμοποιείται αλλά οι νέες εκδόσεις της C
το υποστηρίζουν για λόγους συμβατότητας.
i)
open( ), creat( ), close( )
Αντίθετα από το high-level σύστημα, το low-level
δε χρησιμοποιεί file-pointers του τύπου FILE, αλλά file descriptors οι οποίοι καλούνται handlers και είναι τύπου int. Η δήλωση της open( ) είναι:
int open (char
*filename, int mode, int access);
όπου filename είναι ένα οποιοδήποτε αποδεκτό όνομα αρχείου και mode ένα από τα παρακάτω macros δηλωμένο στο <fcntl.h>.
O_RDONLY Read
Only
O_WRONLY Write Only
O_RDWR Read / Write
Η
παράμετρος access
σχετίζεται μόνο με το UNIX και
εργάζεται μόνο κάτω από αυτό απλώς έχει προστεθεί για συμβατότητα. Η Turbo C υποστηρίζει μία έκδοση της συνάρτησης για DOS η οποία είναι:
int _open (char *filename, int mode);
στην οποία δεν υπάρχει η
παράμετρος access. Συνήθως,
όταν χρησιμοποιούμε την access σε DOS η access τίθεται
στο 0. Μία επιτυχής κλήση της open( )
επιστρέφει ένα θετικό ακέραιο. Αν επιστρέφει –1 σημαίνει ότι το αρχείο δεν
άνοιξε. Συνήθως χρησιμοποιούμε την open( ) ως
εξής:
if ( ( fd = open (“filename”, mode, 0 ) ) == -1)
{
printf (“Cannot Open File \n”);
exit (1);
}
Εάν το
αρχείο filename δεν υπάρχει
στο δίσκο, η διαδικασία αποτυγχάνει ενώ το αρχείο δε δημιουργείται.
Η σύνταξη
της close( ) είναι:
int close (int rd);
Αν η close( ) επιστρέψει –1 σημαίνει ότι στάθηκε αδύνατο να
κλείσει το αρχείο. Αυτό θα μπορούσε να συμβεί αν π.χ. η δισκέτα είχε
μετακινηθεί από το drive.
Μία κλήση
στην close( ) ελευθερώνει τον file descriptor ο οποίος θα μπορούσε να χρησιμοποιηθεί από άλλο αρχείο.
Πάντα υπάρχει ένα όριο στα αρχεία που μπορούν να είναι ταυτόχρονα ανοιχτά.
Έτσι, πρέπει να κλείνουμε ένα αρχείο όταν δε χρειάζεται. Σημαντικό είναι ότι η close( ) αναγκάζει τις πληροφορίες που βρίσκονται στους
εσωτερικούς buffers του
λειτουργικού συστήματος να γραφούν στο δίσκο. Αποτυχία στο κλείσιμο ενός
αρχείου συνήθως οδηγεί στην απώλεια δεδομένων.
Για τη
δημιουργία ενός αρχείου χρησιμοποιούμε τη συνάρτηση creat( ) η οποία είναι δηλωμένη ως εξής:
int creat (char
*filename, int access);
όπου filename οποιοδήποτε αποδεκτό όνομα αρχείου. Το όρισμα access χρησιμοποιείται για να καθορίσει τις μεθόδους
προσπέλασης και να μαρκάρει το αρχείο σαν binary ή text.
Επειδή η
χρήση του access στην creat( ) σχετίζεται με το περιβάλλον του UNIX η C υποστηρίζει μία έκδοση
της συνάρτησης αποκλειστικά για DOS. Αυτή
είναι η _creat( ), η
οποία δέχεται ένα attribute
byte αντί για το όρισμα access. Στο DOS όλα τα
αρχεία σχετίζονται με ένα attribute
byte το οποίο περιέχει διάφορες
πληροφορίες. Η συνάρτηση είναι δηλωμένη ως εξής:
int _creat (char *filename, int attrib)
Το attribute byte αναλύεται:
Bit
0 1 2 3 4 5 6 7 |
Τιμή
1 2 4 8 16 32 64 128 |
Έννοια
Read Only File Hidden File System File Volume Label Name Subdirectory Name Archive Unused Unused |
Οι τιμές
του ανωτέρω πίνακα είναι αθροιστικές. Έτσι, αν θέλουμε να δημιουργήσουμε ένα Read-Only, Hidden αρχείο θα πρέπει να
χρησιμοποιήσουμε την τιμή 3 (1+2). Αυτό είναι λογικό γιατί το attribute byte θα έχει τιμή 00000011, δηλαδή 3 σε δεκαδικό σύστημα.
Πρέπει δηλαδή τα δύο χαμηλής τάξης bit να είναι
ενεργοποιημένα.
Από τη
στιγμή που ένα αρχείο έχει ανοιχτεί για γράψιμο μπορεί να προσπελαστεί με τη write( ). Η δήλωσή της είναι:
int write (int fd, void *buf, int size);
Κάθε φορά
που η write( ) εκτελείται, γράφονται size χαρακτήρες στο αρχείο το οποίο καθορίζεται από το fd, από τον buffer *buf.
Επειδή η write( ) μπορεί να γράψει ένα buffer ο οποίος είναι μισογεμάτος, δεν γράφονται όλα τα
περιεχόμενα του buffer στο
δίσκο. Η συνάρτηση επιστρέφει τον αριθμό των bytes που γράφηκαν έπειτα από μία επιτυχή κλήση της, ενώ σε αποτυχία
επιστρέφεται –1.
Η
συνάρτηση read( )
αποτελεί συμπλήρωμα της write( ). Η δήλωσή της είναι:
int read (int fd, void
*buf, int size);
όπου οι παράμετροι fd, buf και size αντιδρούν όπως και στην write( ) με διαφορά ότι η read( ) τοποθετεί δεδομένα στον buffer που δείχνει ο pointer
buf. Αν η read( ) επιτύχει, επιστρέφει τον αριθμό χαρακτήρων που
διαβάστηκαν, ενώ επιστρέφει –1 σε περίπτωση σφάλματος.
Το
παρακάτω πρόγραμμα δείχνει πως ακριβώς λειτουργούν οι συναρτήσεις του unbuffered I/O system. Διαβάζει γραμμές κειμένου από το πληκτρολόγιο, τις
γράφει σε αρχείο δίσκου και στη συνέχεια τις ξαναδιαβάζει και τις εμφανίζει
στην οθόνη.
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#define BUF_SIZE 128
main ( ) / * Διάβασμα & Γράψιμο Με το Low Level I/O * /
{
char buf
[BUF_SIZE];
int fd1,
fd2, t;
if (
(fd1=creat (“oscar”, O_WRONLY) ) == -1)
{
printf(“Cannot
Open File \n”);
exit(1);
}
input
(buf, fd1);
close
(fd1);
if ( (fd2=open
(“oscar”, O_RDONLY, 0) ) == -1) {
printf(“Cannot
Open File \n”);
exit(1);
}
display
(buf, fd2);
close
(fd2);
}
input (buf, fd1) / *
Ανάγνωση Δεδομένων * /
char *buf;
int fd1;
{
register int t;
do {
for
(t=0; t<BUF_SIZE; t++) buf [t] = ‘ \
0 ‘;
gets (buf); / *
Εισαγωγή χαρακτήρων από το πλ/γιο * /
if (write (fd1, buf, BUF_SIZE) ! = BUF_SIZE) {
printf
(“Error On Write \n”);
exit(1);
}
} while (strcmp (buf, “Quit”) );
}
display (buf, fd2)
char *buf;
int fd2;
{
for (;
;) {
if
(read (fd2, buf, BUF_SIZE) == 0)
return;
}
}
Εάν
θέλουμε να διαγράψουμε ένα αρχείο από το directory χρησιμοποιούμε την unlink( ). Η δήλωσή της είναι:
int unlink (char *filename);
όπου filename ένας char
pointer σε
οποιοδήποτε αποδεκτό όνομα αρχείου. Η συνάρτηση επιστρέφει ένα σφάλμα (συνήθως
–1) αν σταθεί αδύνατη η διαγραφή του αρχείου. Αυτό μπορεί να συμβεί αν δε
βρεθεί το αρχείο ή αν η δισκέτα είναι write
protected.
ii)
Χαμηλού
επιπέδου τυχαία προσπέλαση
Η Turbo C επιτρέπει τυχαία προσπέλαση Ι/Ο σε χαμηλό επίπεδο
μέσω της συνάρτησης lseek( ). Η δήλωσή της είναι:
long lseek (int fd, long num_nytes, int origin);
όπου fd ένας file
descriptor ο οποίος
επιστρέφεται από μία creat( ) ή open( ). Το num_bytes πρέπει να είναι ένας long int. Το origin είναι ένα από τα ακόλουθα macros:
Beginning Of File SEEK_SET
Current Position SEEK_CUR
End
Of File SEEK_END
Για να
αναζητήσουμε num_bytes από την αρχή του αρχείου το origin θα πρέπει να τεθεί SEEK_SET. Για την
αναζήτηση από το τέλος του αρχείου το SEEK_END. Η lseek( )
επιστρέφει offset σε
περίπτωση επιτυχίας. Γενικά, η lseek( ), επιστρέφει long int και
πρέπει να έχει δηλωθεί κατ’ αυτό τον τρόπο στην αρχή του προγράμματος. Σε
αποτυχία επιστρέφει –1L.
Ένα απλό
παράδειγμα είναι το πρόγραμμα DUMP
σχεδιασμένο για UNIX-LIKE I/O system. Χρησιμοποιεί όχι μόνο την lseek( ) αλλά και άλλες συναρτήσεις του low-level
συστήματος.
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <cytpe.h>
#include <fcnt1.h>
#define SIZE 128
char buf [SIZE];
void display( );
main(argc, argv)
int argc;
char *argv[ ];
{
char
s[10];
int fd,
sector, numread;
long
pos;
if (argc
! = 2) {
printf(
“You Forgot To Enter File Name”);
exit(1);
}
if ( ( fd
= open (argv [1], O_RDONLY, 0) ) = -1) {
printf
(“Cannot Open File \n”);
exit
(1);
}
do {
printf
(“\n \n buffer : “);
gets
(s) ;
sector
= atoi (s) ; / * Get Sector To Read * /
pos
= (long) (sector *
SIZE);
if
(lseek( fd, pos, SEEK_SET) !=pos)
printf (“Seek Error \n”);
if
(numread = read (fd, buf, SIZE) ) ! = SIZE)
printf
(“EOF Reached \n”)
display
(numread);
} while (sector >= 0);
close
(fd);
}
void display (int numread)
{
int i,
j;
for (i =
0; i < numread / 16; i++) {
for
(j=0; j<16; j++) printf (“%3X”, buf[ i * 16 + j ] ) ;
printf
(“ “);
for
(j=0; j<16; j++) {
if
(isprintf (buf [i * 16 + j ]) ) printf
( “%c”, buf ( i * 16 + j]);
else printf (“ . “);
}
printf ( “\n”);
}
}
Το ανωτέρω
πρόγραμμα εκτελεί μία λειτουργία dump, όμοια με
τον debuger του DOS, κάνοντας χρήση του Low Level
I/O συστήματος. Το ίδιο πρόγραμμα υπάρχει και σε high level I/O σύστημα στο αντίστοιχο κεφάλαιο.
Το buffered I/O System, το οποίο έχει οριστεί από την ANSI, προτείνεται για νέα projects. Επειδή η επιτροπή ANSI προτίμησε να μην τυποποιήσει το UNIX-like σύστημα,
δε μπορούμε να το προτείνουμε για μελλοντικά projects. Πάντως, ο ήδη υπάρχων κώδικας, θεωρείται διατηρήσιμος για
αρκετά χρόνια ακόμη. Σίγουρα αυτή τη στιγμή δεν υπάρχει λόγος να σπεύσουμε σε
αλλαγή του. Πάντως θα πρέπει να προσέξουμε το εξής: Δεν πρέπει ποτέ να
προσπαθήσουμε να ανοίξουμε τα δύο συστήματα μέσα στο ίδιο πρόγραμμα. Επειδή οι
τρόποι προσέγγισης των αρχείων από αυτά είναι διαφορετικοί, ενδέχεται να
υπάρξουν σημαντικές επιπλοκές.