#include <stdio.h>
Πολλές φορές είναι χρήσιμη η καταγραφή των σφαλμάτων σε ένα πρόγραμμα C. Η πρότυπη συνάρτηση βιβλιοθήκης perror() είναι εύχρηστη και βολική. Χρησιμοποιείται σε συνδυασμό με το errno . Αν και δεν είναι αυστηρά τμήμα της βιβλιοθήκης stdio.h εισάγουμε την χρήση του errno και της συνάρτησης exit().
Η συνάρτηση perror() δηλώνεται ως εξής:
void perror(const char *message);
Η perror() παράγει ένα μήνυμα (στο πρότυπο σφάλμα, stderr) που περιγράφει το τελευταίο σφάλμα που συνέβη κατά τη κλήση μιας συνάρτησης βιβλιοθήκης ή κλήσης συστήματος και το οποίο επέστρεψε έναν αριθμό σφάλματος errno . Το όρισμα message, εμφανίζεται πρώτο, μετά ένα ερωτηματικό και ένα κενό. Κατόπιν ακολουθεί το μήνυμα του συστήματος που αντιστοιχεί στο errno . Αν το message είναι δείκτης NULL ή είναι κενό, το ερωτηματικό δεν εμφανίζεται, αλλά μόνο τοι μήνυμα του συστήματος.
Επίσης υπάρχουν και ορισμένες άλλες συναρτήσεις εμφάνισης σφαλμάτων, όπως η strerror() η οποία αποθηκεύει τα μηνύματα σε απομονωτή που επιλέγει ο προγραμματιστής. Αυτές οι συναρτήσεις βρίσκονται στη string.h.
H errno είναι ειδική μεταβλητή του συστήματος που λαμβάνει τιμή από το σύστημα όταν αυτό δεν μπορεί να εκτελέσει κάποια εργασία. Ορίζεται στη errno.h. Οι αριθμοί σφάλματος αντιστοιχούν σε error codes μέσω μακροεντολών που περιγράφονται στο εγχειρίδιο της πρότυπης βιβλιοθήκης.
Η χρήση της errno σε ένα
πρόγραμμα απαιτεί τη δήλωσή της στο πρόγραμμα ως:
volatile int errno;
Μπορεί να τροποποιηθεί από το πρόγραμμα, αλλά αυτό είναι σπάνιο.
Η συνάρτηση exit() δηλώνεται μέσω του #include
<stdlib.h>
ως:
void exit(int status)
Η exit() απλά τερματίζει την εκτέλεση ενός προγράμματος και επιστρέφει την τιμή της κατάστασης εξόδου status στο λειτουργικό σύστημα. Η τιμή status δείχνει τον κανονικό ή μή τερματισμό του προγράμματος. Συμβατικά υπάχρουν δύο προεπιλεγμένες μακροεντολές για τις τιμές επιτυχούς και ανεπιτυχούς τερματισμού:
Σε πιο σύνθετες καταστάσεις η τιμή του status μπορεί να λάβει τιμές από 0 έως 255 οι οποίες να ελέγχονται από το σύστημα κα να υπάρχει σχετική απόκριση. Συνήθως όμως απλά καλούμε exit(EXIT_FAILURE) όταν εμφανιστεί σφάλμα ή exit(EXIT_SUCCESS) για επιυχή τερματισμό.
Οι Ροές (Streams) είναι
μια αφαίρεση (abstrcation) της
ανάγνωσης και εγγραφής δεδομένων ώστε αυτές να είναι σχετικά
ανεξάστητες από το σύστημα, ώστε η Ε/Ε να είναι ταυτόχρονα ευέλικτη
αλλά και αποδοτική.
Μια Ροή είναι είτε ένα αρχείο ή μια φυσική
συσκευή (π.χ.
πληκτρολόγιο ή οθόνη). Και οι δύο εκδοχές αναπαριστώνται προς το
προγραμματιστή με την ίδια μορφή (αφαίρεση), δηλαδή ως ένας δείκτης
σε ένα ειδικό αρχείο, τη ροή.
Στη C υπάρχει μια εσωτερική δομή δεδομένων (δείκτης
σε απομονωτή αρχείου ή συσκευής), η FILE, που
αναπαριστά όλες τις ροές και ορίζεται στη stdio.h.
Για να επιτύχουμε Ε/Ε με ροές στη C απλά αναφερόμαστε στη κατάλληλη
δομή τύπου FILE.
Απλά, στο πρόγραμμά μας δηλώνουμε ένα δείκτη σε μια δομή τύπου
FILE. Δεν απαιτούνται περισσότερες
λεπτομέρειες για το τύπο του αρχείου (ή ροής).
Πριν από την εκτέλεση οποιασδήποτε λειτουργίας Ε/Ε πρέπει
να ανοίξουμε (open) το αρχείο (ή τη ροή),
δηλαδή να συσχετίσουμε τη λογική αφαίρεση (τον δείκτη σε δομή τύπου FILE)
με ένα φυσικό αντικείμενο (π.χ. όνομα διαδρομής απλού αρχείου ή
δεσμευμένο όνομα αρχείου-συσκευής).
Κατόπιν μπορούμε να προσπελάσουμε
τη ροή με συναρτήσεις ανάγνωσης, εγγραφής αρχείων κλπ.
Στο τέλος του προγράμματος (συνήθως) πρέπει να
κλείσουμε τη ροή.
Η Ε.Ε ροών (άρα και αρχείων) είναι Απομονωμένη (Buffered): Αυτό σημαίνει οτι η ανάγνωση και η εγγραφή πραγματοποιείται σε σταθερά τμήματα (block) δεδομένων, τα οποία ενδιάμεσα αποθηκέυονται σε χώρο προσωρινής αποθήκευσης στη κύρια μνήμη (τον απομονωτή, buffer). Αυτό φαίνεται στην εικόνα που ακολουθεί. Ο περιγραφέας αρχείου επομένως δείχει τον απομονωτή.
Μοντέλο Ε/Ε ροών και αρχείων
Αυτή η λύση οδηγεί σε αποδοτική και ευέλικτη Ε/Ε αλλά προσοχή: τα δεδομένα που γράφονται σε ένα απομονωτή δεν εμφανίζονται στο αρχείο (ή τη συσκευή) μέχρι ο απομονωτής να αδειάσει (flush). Στις συσκευές χαρακτήρων (πληκτρολόγιο, οθόνη κειμένου) αυτό γίνεται με την αλλαγή γραμμής (ENTER, n ). Σε συσκευές block (όπως οι δίσκοι) μπορούμε να ορίσουμε μέγεθος block ή και να επιβάλλουμε ενιδάμεσο άδειασμα του αν απαιτείται (fflush())
stdin, stdout, stderr
Όλες είναι ροές που αντιστοιχούν σε συκευές χαρακτήρων.
Οι προεπιλεγμένες τιμές είναι η οθόνη κειμένου για stdout και stderr, το πληκτρολόγιο για το stdin. Τα stdin και stdout μπορούν να συνδεθούν με (ανακατευθυνθούν σε) αρχεία, προγράμματα, συσκευές Ε/Ε, κλπ. Το stderr πάντα οδηγεί στην οθόνη. Οι προεπιλεγμένες ροές είναι αυτόματα ανοικτές, δεν απαιτείται να τις ανοίγουμε στο πρόγραμμά μας.
Μια τυπική σύνδεσή με περιγραφείς αρχείων φαίνεται πρακάτω. Το stdin έχει τη τιμή 0, το stdout 1 και το stderr 2./dev/stderr
-> /proc/self/fd/2
/dev/stdin ->
/proc/self/fd/0
/dev/stdout
-> /proc/self/fd/1
Η ανακατεύθυνση των προεπειλεγμένων ροών είναι δυνατή σε επίπεδο φλοιού UNIX/Linux στη γραμμή εντολών:
> -- stdout
προς αρχείο.
Ένα πρόγραμμα program, αντί να στείλει την έξοδο στην οθόνη τη στέλνει σε ένα αρχείο file1.
program > file1
< -- stdin
από αρχείο.
Ένα πρόγραμμα program, αντί να δεχτεί
είσοδο από το πληκτρολόγιο, δέχεται από το αρχείο file2.
program < file2.
| -- σωλήνωσηe: η
έξοδος stdout γίνεται η είσοδος stdin
του άλλου
prog1 | prog2
Πιθανότατα οι πιο κοινές συναρτήσεις είναι οι getchar() και putchar(). Όρίζονται ως εξής:
int ch;
ch = getchar();
(void) putchar((char) ch);
Παρόμοιες γενικευμένες συναρτήσεις:
int getc(FILE *stream)
int putc(char ch,FILE *stream)
int printf(char *format, arg list ...) --
γράφει στο stdout
τη λίστα των ορισμάτων σύμφωνα με την αντίστοιχη συμβολοσειρά
μορφοποίησης. Επιστρέφει τον αριθμό των χαρακτήρων που τυπώθηκαν.
Η συμβολοσειρά μορφοποίησης έχει 3 τύπους αντικειμένων:
\e Write an <escape> character.
\a Write a <bell> character.
\b Write a <backspace> character.
\f Write a <form-feed> character.
\n Write a <new-line> character.
\r Write a <carriage return> character.
\t Write a <tab> character.
\v Write a <vertical tab> character.
\' Write a <single quote> character.
\\ Write a backslash character.
\num Write an 8-bit character whose ASCII value is the 1-, 2-,
or 3-digit octal number num.
Format Spec (%) | Type | Result |
c | char | single character |
i,d | int | decimal number |
o | int | octal number |
x,X | int | hexadecimal number |
lower/uppercase notation | ||
u | int | unsigned int |
s | char * | print string |
terminated by 0 | ||
f | double/float | format -m.ddd... |
e,E | " | Scientific Format |
-1.23e002 | ||
g,G | " | e or f whichever |
is most compact | ||
% | - | print % character |
Έτσι η κλήση:
printf("%-2.3fn",17.23478);
θα έχει ως αποτέλεσμα στην οθόνη:
17.235ενώ η κλήση:
printf("VAT=17.5%%n");εμφανίζει:
VAT=17.5%
Η συνάρτηση αυτή είναι η αντίστοιχη της printf() για
ανάγνωση και ορίζεται ως:
int scanf(char *format, args....) --
διαβάζει από το stdin
και αποθηκεύσει στις μεταβλητές που δηλώνονται στη λίστα των ορισμάτων
σύμφωνα με την αντίστοιχη συμβολοσειρά μορφοποίησης. Επιστρέφει τον
αριθμό των χαρακτήρων που διαβάστηκαν.
Για τη μορφοποίηση ισχύουν ότι για τη printf()
Προσοχή:
στη λίστα ορισμάτων της
scanf() απαιτούνται διευθύνσεις των
μεταβλητών ή δείκτες
σε μεταβλητές.
scanf(``%d'',&i);
Θυμηθείτε οτι τα ονόματα συμολοσειρών ή πινάκων είναι δείκτες.
char string[80];
scanf(``%s'',string);
Τα αρχεία αποτελούν τη συνηθέστερη μορφή ροής, με δεδομένο οτι ακόμη και οι συσκευές στο UNIX/Linux αναπαριστώνται ως αρχεία.
Κατ' αρχήν πρέπει να ανοίξουμε ένα
αρχείο. Η συνάρτηση fopen()
είναι ως εξής:
FILE *fopen(char
*name, char *mode)
Η fopen επιστρέφει ένα δείκτη σε δομή
τύπου FILE,
στην ουσία είναι ο δείκτης στα περιεχόμενα του αντίστοιχου αρχείου
(μέσω του απομονωτή αρχείου). Η συμβολοσειρά name
είναι το όνομα διαδρομής του αρχείου που θα προσπελαστεί, έτσι ώστε το
σύστημα συνδέει το συγκεκριμένο αρχείο με τον αντίστοιχο απομονωτή. Η
συμβολοσειρά mode ελέγχει το τύπο
προσπέλασης. Οι τύποι προσπέλασης είναι:
Αν για κάποιο λόγο το αρχείο δεν μπορεί να προσπελαστεί, επιστρέφεται ένας δείκτης NULL.
Το UNIX/Linux γενικά θεωρεί όλα τα αρχεία (ροές) ως ακολουθίες bytes (χαρακτήρων), και έτσι τα χειρίζεται. Αν θέλουμε να προσπελάσουμε ένα αρχείο ή μια ροή σε μορφή δυαδική (δηλαδή οργανωμένο σε εγγραφές και block εγγραφών) τότε πρέπει να προσθέσουμε στο τέλος του αντίστοιχου τύπου προσπέλασης το b (binary), δηλαδή π.χ. αντί για "w+" έχουμε "w+b". Προσοχή οτι στη συνέχεια ένα τέτοιο αρχείο πρέπει να προσπελαύνεται με ειδικές συναρτήσεις που θα συζητηθούν παρακάτω, και όχι με αυτές που αφορούν απλή ή μορφοποιημένη προσπέλαση χαρακτήρων.
Έτσι το άνοιγμα ενός αρχείου myfile.dat για ανάγνωση είναι:
FILE *stream, *fopen();
/* declare a stream and prototype fopen */
stream = fopen("myfile.dat","r");
ενώ το άνοιγμα ενός δυαδικού αρχείου myfile.dat για ανάγνωση είναι:
FILE *stream, *fopen();
/* declare a stream and prototype fopen */
stream = fopen("myfile.dat","rb");
Είναι καλή πρακτική να ελέγχουμε αν το αρχείο έχει ανοίξει
σωστά:
if ( (stream = fopen( "myfile.dat","r")) == NULL)
{ printf(``Can't open %sn'',"myfile.dat");
exit(1);
}
......
Το κλείσιμο ενός αρχείου είναι απλό:
fclose(FILE *stream)
Οι συναρτήσεις fprintf και fscanf χρησιμοποιούνται συχνότερα:
int fprintf(FILE *stream, char *format, args..)
int fscanf(FILE *stream, char *format, args..)
Είναι παρόμοιες με τις printf και scanf
μόνο που τα δεδομένα σχετίζονται με μια ροή που πρέπει να ανοίξει με fopen(). Ο δείκτης στη ροή (ή στο αρχείο, γνωστός και ως περιγραφέας αρχείου, file descriptor) μεταβάλλεται αυτόματα με κάθε λειτουργία εγγραφής/ενάγνωσης αρχείου, έτσι ώστε δείχνει πάντα στη τρέχουσα θέση προς επεξεργασία. Δεν χρειάζεται να ανησυχούμε γι' αυτό.
char *string[80]
Συναρτήσεις χαρατήρων:
FILE *stream, *fopen();
if ( (stream = fopen(...)) != NULL)
fscanf(stream,``%s'', string);
int getc(FILE *stream), int fgetc(FILE
*stream)
int putc(char ch, FILE *s), int
fputc(char ch, FILE *s)
Αυτές είναι παρόμοιες με τις getchar(), putchar(). H getc() ορίζεται ως μακροεντολή στο stdio.h. H fgetc() είναι κανονική συνάρτηση βιβλιοθήκης. Έχουν το ίδιο αποτέλεσμα.
Συναρτήσεις συμβολοσειρών:
char *fgets(char *s, int count, FILE *stream)
int fputs(char *s, FILE *s)
όπου s είναι η συμβολοσειρά και count ο μέγιστος αριθμός των προς ανάγνωση χαρακτήρων.
Υπάρχουν και αντίστοιχες συναρτήσεις για ανάγνωση/εγραφή πλήρους γραμμής (μέχρι το χαρακτήρα αλλαγής γραμμής \n) και διαχωρισμού του περιεχομένου σε υπο-συμβολοσειρές κλπ.
Σε περίπτωση που θέλουμε να αδειάσουμε τον απομονωτή χωρίς να υπάρχει αλλαγή γραμμής:
fflush(FILE *stream) -- άδειασμα απομονωτή.
Οι γενικευμένες συναρτήσεις αρχείων (όπως η fprintf() κλπ) μπορούν να χρησιμοποιηθούν και για τις πρότυπες ροές, αφού έχουν προεπιλεγμένες τιμές περιγραφέων αρχείων.
fprintf(stderr,"Cannot
Compute!!n");
fscanf(stdin,"%s",string);
Η ανάγνωση/εγγραφή σε δυαδικά αρχεία γίνεται ως εξής:
size_t fwrite (const void *data, size_t size, size_t count, FILE *stream)
size_t fread (void *data, size_t size, size_t count, FILE *stream)
όπου *data είναι δείκτης στα δεδομένα τύπου struct size_t (αριμός bytes size σε κάθε στοιχείο δομής τύπου size_t). Επιτρέπεται η ανάγνωση/εγγραφή ενός block από count δομές size_t, δηλαδή size*count bytes.
Είναι δυνατή η χρήση των παραπάνω συναρτήσεων σε αρχεία χαρακτήρων ως εξής:
fread (large_string, 1, 1000, stream)
Υπάρχουν ορισμένες χρήσιμες συναρτήσεις που μας ενημερώνουν για τη κατάσταση μια ροής:
int feof(FILE *stream);
int ferror(FILE *stream);
void clearerr(FILE *stream);
int fileno(FILE *stream);
Η χρήση τους είναι ο απλή:
while ( !feof(fp) )
fscanf(fp,"%s",line);
long int ftell(FILE *stream);
inf fseek (FILE *stream, long int offset, int whence);
int rewind (FILE *stream);
SEEK_SET
, SEEK_CUR
, ή SEEK_END
(πρώτη, τρέχουσα και τελευταία θέση του fp στο αρχείο).
Μοιάζουν με τις fprintf και fscanf
αλλά γράφουν/διαβάζουν σε συμβολοσειρές.
int sprintf(char *string, char *format, args..)
int sscanf(char *string, char *format, args..)
Παράδειγμα:
float full_tank = 47.0; /* litres */
float miles = 300;
char miles_per_litre[80];
sprintf(miles_per_litre,``Miles per litre= %2.3f'', miles/full_tank);
Στην Ε/Ε Χαμηλού Επιπέδου δεν υπάρχει απομονωτής--
κάθε λειτουργία ανάγνωσης/εγγραφής καταλήγει σε άμεση προσπέλαση της
αντίστοιχης συσκευής (πληκτρολογίου, οθόνης, δίσκου κλπ), και την άμεση
μεταφορά του συγκεκριμένου αριθμού bytes.
Επίσης δεν υπάρχει μορφοποίηση, μιλούμε πάντα για απλά bytes. Επομένως όλα τα αρχεία αντιμετωπίζονται ως δυαδικά, (επίθεμα b στο fopen()) και όχι ως αρχεία κειμένου.
Ο δείκτης σε τύπο FILE αντικαθίσταται με το χαμηλού επιπέδου αριθμητικό αντίστοιχό του (περιγραφέα αρχείου, file descriptor/hadle) που ουσιαστικά αντιστοιχεί σε έναν ακέραιο. Θυμηθείτε οτι το stdin αντιστοιχεί στο 0, το stdout στο 1 και το stderr στο 2.
Άνοιγμα αρχείου σε χαμηλό επίπεδο:
int open(char
*filename, int flag, mode_t mode) -- επιστρέφει τον περιγραφέα ή -1 για αποτυχία.
Αντί για -1 μπορεί να ρυθμιστεί να επιστρέφει errno για κωδικούς λαθών σχετικών με αρχεία
Το flag ελέγχει τους τύπους προσπέλασης, όπως καθορίζεται στη
fcntl.h:
O_APPEND, O_CREAT,
O_EXCL, O_RDONLY, O_RDWR, O_WRONLY + ...
Πρόκειται για bit mask που ρυθμίζει τη δημιουργία και τη λειτουργία των
αρχείων, (δικαιώματα, άδειες κλπ.) Δείτε τις σχετικές σελίδες
τεκμηρίωσης.
mode -- συνήθως 0 στις περισσότερες εφαρμογές.
Η creat() cδεν χρησιμοποιείται πλέον.
Κλείσιμο αρχείου:
int close(int handle)
Ανάγνωση/Εγγραφή:
int read(int handle, char *buffer, unsigned length)
int write(int handle, char *buffer, unsigned length)
διαβάζουν/γράφουν length αριθμό από bytes από/προς το αρχείο (handle) προς/από τη θέση μνήμης buffer.
Η συνάρτηση sizeof() συνήθως χρησιμοποιείται στο length για να μεταφράσει τύπους και δομές δεδομένων σε bytes.
Οι read() και write() επιστρέφουν τον αριθμό των bytes ή -1 για αποτυχία.
Παράδειγμα:
/* program to read a list of floats from a binary file */
/* first byte of file is an integer saying how many */
/* floats in file. Floats follow after it, File name got from */
/* command line */
#include<stdio.h>
#include<fcntl.h>
float bigbuff[1000];
main(int argc, char **argv)
{
int fd;
int bytes_read;
int file_length;
if ( (fd = open(argv[1],O_RDONLY)) = -1)
{ /* error file not open */....
perror("Datafile");
exit(1);
}
if ( (bytes_read = read(fd,&file_length, sizeof(int))) == -1)
{ /* error reading file */...
exit(1);
}
if ( file_length > 999 )
{/* file too big */ ....
}
if ( (bytes_read = read(fd,bigbuff, file_length*sizeof(float))) == -1)
{ /* error reading open */...
exit(1);
}
}
Η συνάρτηση fdopen() επιτρέπει τη σύνδεση ενός περιγραφέα αρχείου handle
με ένα δείκτη σε αρχείο (ροή), έτσι ώστε οι δύο μέθδοι προσπέλασης
αρχείων (υψηλού και χαμηλού επιπέδου) μπορούν να αναμιχθούν. Η
συμβολοσειρά mode έχει την ίδια σύνταξη και σημασία όπως στη συνάρτηση fopen().
Άσκηση 12573
Γράψτε ένα πρόγραμμα που να αντιγράφει ένα αρχείο σε άλλο. Τα ονόματα των δύο αρχείων δίνονται ως ορίσματα. Η αντιγραφή να γίνεται σε blocks των 512 bytes. Να γίνουν όλοι οι έλεγχοι ορισμάτων, ανοίγματος και προσπέλασης αρχείων κλπ.
Άσκηση 12577
Γράψτε ένα πρόγραμμα που να εμφανίζει τις τελευταίες n γραμμές από ένα αρχείο κειμένου, όπου το όνομα του αρχείου δίνονται ως ορίσματα. Το n ως προεπιλογή είναι 5, αλλά μπορεί να τροποποιηθεί με μια επιλογή στη μορφή
last -n file.txt
όπου το n είναι κάποιος ακέραιος. Σκεφτείτε όσο το δυνατό περισσότερους ελέγχους.
Άσκηση 12578
Γράψτε ένα πρόγραμμα που συγκρίνει δύο αρχεία κειμένου και εμφανίζει τις γραμμές που διαφέρουν. Χρησιμοποιείστε τις κατάλληλες συναρτήσεις σύγκρισης συμβολοσειρών.