17. Βιβλιοθήκες
Σύνοψη Η πρότυπη βιβλιοθήκη της C, το αρχείο επικεφαλίδας stdio.h, οι συναρτήσεις snprintf(), scanf(), perror(), το αρχείο επικεφαλίδας stdlib.h, οι συναρτήσεις calloc(), realloc(), abort(), atexit(), srand(), rand(), system(), bsearch(), qsort(), το αρχείο επικεφαλίδας string.h, οι συναρτήσεις strncat(), strncmp(), strncpy(), strdup(), strndup(), memset(), memcmp(), memcpy(), memmove(), το αρχείο επικεφαλίδας math.h, οι συναρτήσεις ceil(), floor(), trunc(), round(), modf(), fmod(), sin(), cos(), tan(), asin(), acos(), atan(), το αρχείο επικεφαλίδας tgmath.h, το αρχείο επικεφαλίδας time.h, οι τύποι clock_t, struct tm, οι συναρτήσεις ctime(), difftimef(), gmtime(), localtime(), asctime(), strftime(), η σταθερά CLOCKS_PER_SEC, εξωτερικές βιβλιοθήκες, το λογισμικό vcpkg, παράδειγμα κλήσης συναρτήσεων της βιβλιοθήκης GSL.
Προαπαιτούμενη γνώση Τύποι δεδομένων, είσοδος/έξοδος, δομές επιλογής και επανάληψης, συναρτήσεις, πίνακες, δείκτες, αλφαριθμητικά, διαμερισμός κώδικα.
17.1 Η πρότυπη βιβλιοθήλκη της C
Η πρότυπη βιβλιοθήκη της C, η libc, είναι μια συλλογή από συναρτήσεις, μακροεντολές και τύπους που αποτελεί τμήμα των προδιαγραφών της γλώσσας. Οι συναρτήσεις της πρότυπης βιβλιοθήκης υλοποιούν βασικές λειτουργίες που χρησιμοποιούνται συχνά και καθώς συμπεριλαμβάνονται στη γλώσσα δεν χρειάζεται να υλοποιηθούν εκ νέου από τον προγραμματιστή ούτε να αναζητηθούν εξωτερικές βιβλιοθήκες για αυτές. Η πρότυπη βιβλιοθήκη περιλαμβάνει διάφορα αρχεία επικεφαλίδων όπως τα stdio.h, stdlib.h, string.h, math.h, time.h και άλλα, με το καθένα να παρέχει διαφορετικά σύνολα συναρτήσεων, τύπων και μακροεντολών.
17.1.1 stdio.h
Το αρχείο επικεφαλίδας stdio.h περιέχει συναρτήσεις όπως οι printf(), scanf(), fopen(), fclose(), fprintf() και fscanf() που είδαμε σε προηγούμενα κεφάλαια. Το stdio.h περιέχει πολλές άλλες χρήσιμες συναρτήσεις. Στη συνέχεια θα παρουσιαστούν 3 από αυτές, η snprintf(), η sscanf() και η perror().
snprintf() Η συνάρτηση snprintf() λειτουργεί παρόμοια με την printf(), αλλά αποθηκεύει το μορφοποιημένο αλφαριθμητικό που παράγει σε ένα αλφαριθμητικό αντί να το εμφανίσει στην οθόνη. Τα τρία πρώτα υποχρεωτικά ορίσματα που δέχεται είναι: α) ένα αλφαριθμητικό εξόδου, β) το μέγιστο πλήθος χαρακτήρων που θα γραφούν στο αλφαριθμητικό εξόδου, συμπεριλαμβανομένου του συμβόλου '\0', και γ) ένα αλφαριθμητικό μορφοποίησης, όπως αυτά που χρησιμοποιεί η printf(). Επιστρέφει το πλήθος των χαρακτήρων που γράφονται στο αλφαριθμητικό εξόδου. Στον κώδικα 17.1 παρουσιάζεται ένα απλό παράδειγμα που εγγράφει σε ένα αλφαριθμητικό έναν ακέραιο και δύο πραγματικούς αριθμούς. Στη συνέχεια, το αλφαριθμητικό εμφανίζεται στην οθόνη.
Κώδικας 17.1: ch17_p1.c - χρήση αλφαριθμητικού για την έξοδο τιμών. | |
---|---|
Ακολουθούν τα αποτελέσματα εκτέλεσης του κώδικα:
Αξίζει να σημειωθεί ότι ισοδύναμη λειτουργικότητα παρέχει και η συνάρτηση sprintf(), αλλά πλέον είναι deprecated (δηλαδή η χρήση της δεν συνίσταται και μπορεί να αποσυρθεί σε μελλοντικές εκδόσεις). Η προειδοποίηση που επιστρέφει ο μεταγλωττιστής gcc, αν χρησιμοποιηθεί ο διακόπτης ‐fsanitize=address, είναι να μη χρησιμοποιείται η sprintf(), λόγω του ότι δεν παρέχει έλεγχο για πιθανή υπερχείλιση του αλφαριθμητικού στο οποίο κατευθύνεται η έξοδος.
sscanf() Η συνάρτηση sscanf() λειτουργεί παρόμοια με την scanf(), αλλά αντί να διαβάζει την είσοδο από το πληκτρολόγιο, διαβάζει από ένα αλφαριθμητικό. Δέχεται δύο κύρια ορίσματα, ένα αλφαριθμητικό εισόδου και ένα αλφαριθμητικό μορφοποίησης που ορίζει την αναμενόμενη μορφή των δεδομένων εισόδου, όπως στην scanf(). Η sscanf() διαβάζει δεδομένα από το αλφαριθμητικό εισόδου και αναθέτει τιμές με βάση το αλφαριθμητικό μορφοποίησης σε αντίστοιχες μεταβλητές που περνούν ως επιπλέον ορίσματα μετά το αλφαριθμητικό μορφοποίησης. Η συνάρτηση επιστρέφει το πλήθος των εισόδων που αντιστοιχίστηκαν επιτυχώς και ανατέθηκαν ή EOF αν συμβεί κάποιο σφάλμα (π.χ. κενό αλφαριθμητικό εισόδου). Ο κώδικας 17.2 είναι ένα παράδειγμα χρήσης της sscanf(), όπου η είσοδος τιμών για 3 μεταβλητές δίνεται από ένα αλφαριθμητικό.
Η εκτέλεση του προγράμματος θα δώσει την ακόλουθη έξοδο:
perror() Ορισμένες συναρτήσεις της πρότυπης βιβλιοθήκης, αλλά και συναρτήσεις άλλων βιβλιοθηκών, έχουν γραφεί έτσι ώστε όταν προκαλείται ένα σφάλμα η καθολική μεταβλητή errno που ορίζεται στο errno.h να λαμβάνει μια αναγνωριστική τιμή σφάλματος. Η διαδικασία που ακολουθείται για τον χειρισμό λαθών που θέτουν τιμή στην errno είναι η ακόλουθη:
- Κλήση συνάρτησης που μπορεί να προκαλέσει κάποια κατάσταση σφάλματος.
- Αν το σφάλμα προκληθεί, τότε η συνάρτηση επιστρέφει μια τιμή που υποδηλώνει ότι συνέβη πρόβλημα (π.χ. NULL ή -1 ή κάποια άλλη τιμή).
- Ο κώδικας που κάλεσε τη συνάρτηση ελέγχει την τιμή του errno για να καθορίσει τον τύπο του σφάλματος.
- Χειρισμός λάθους (π.χ. εμφάνιση μηνύματος λάθους, καταγραφή λάθους, τερματισμός προγράμματος).
Η συνάρτηση perror() χρησιμοποιείται κυρίως για να εμφανίσει μηνύματα αποσφαλμάτωσης. Η κλήση της εμφανίζει ένα μήνυμα που ορίζει ο προγραμματιστής που ακολουθείται από τον χαρακτήρα : και την περιγραφή του πλέον πρόσφατου σφάλματος που συνέβη και έθεσε την τιμή του errno.
Παραδείγματα συναρτήσεων που μπορεί να προκαλέσουν σφάλματα κατά την κλήση τους είναι η fopen() της stdio.h, η malloc() της stdlib.h, η read() της unistd.h και άλλες. Στον κώδικα 17.3 παρουσιάζεται ένα παράδειγμα που η fopen() θέτει την τιμή του errno επιχειρώντας να ανοίξει ένα αρχείο που δεν εντοπίζεται.
Κώδικας 17.3: ch17_p3.c - αποτυχημένο άνοιγμα αρχείου και χειρισμός του λάθους που προκύπτει. | |
---|---|
Η έξοδος του προγράμματος, δεδομένου ότι το αρχείο non_existent_file.txt δεν υπάρχει στο σύστημα είναι:
17.1.2 stdlib.h
Το stdlib.h περιέχει συναρτήσεις για δέσμευση και αποδέσμευση μνήμης όπως η malloc() και η free(), συναρτήσεις για έλεγχο διεργασιών όπως η exit(), συναρτήσεις μετατροπής αλφαριθμητικού σε ακέραιο όπως η atoi(), συναρτήσεις παραγωγής τυχαίων τιμών όπως η rand() και η srand(), καθώς και άλλες συναρτήσεις. Στη συνέχεια θα εξεταστούν μερικές βασικές δυνατότητες που προσφέρει η stdlib.h.
Δυναμική δέσμευση μνήμης Εκτός από τη malloc() υπάρχουν και άλλες συναρτήσεις δυναμικής δέσμευσης μνήμης όπως η calloc() και η realloc(), όπως είδαμε στην παράγραφο 9.1.5. H calloc() δέχεται δύο ορίσματα, το πλήθος των στοιχείων που θα δεσμεύσει σε συνεχόμενες θέσεις μνήμης και το μέγεθος σε bytes κάθε στοιχείου. Επιστρέφει έναν δείκτη προς το πρώτο byte του μπλοκ μνήμης που δεσμεύει, αρχικοποιώντας όλα τα bytes σε μηδέν. Αν αποτύχει η δέσμευση μνήμης, η calloc() επιστρέφει την τιμή NULL. Η realloc() χρησιμοποιείται για να αλλάξει μέγεθος σε μνήμη που έχει δεσμευθεί σε προηγούμενη δέσμευση μνήμης. Δέχεται ως ορίσματα έναν δείκτη προς δεσμευμένη μνήμη και το μέγεθος της μνήμης που θα δεσμευθεί. Η realloc(), στην περίπτωση που αυξάνει το μέγεθος της μνήμης που δεσμεύει σε σχέση με πριν, διατηρεί τα δεδομένα της προηγούμενης δεσμευμένης μνήμης. Ακολουθεί, στον κώδικα 17.4, ένα παράδειγμα που αρχικά δεσμεύεται δυναμικά χώρος μνήμης για έναν πίνακα 5 ακεραίων, με την calloc() αρχικοποιώντας όλες τις τιμές του πίνακα με μηδέν. Στη συνέχεια, με τη realloc() ο δεσμευμένος χώρος επεκτείνεται έτσι ώστε να διατηρεί πλέον 10 θέσεις ακεραίων
Η εκτέλεση του προγράμματος δίνει τα ακόλουθα αποτελέσματα:
Αξίζει να σημειωθεί ότι οι 5 πρώτες τιμές του πίνακα αναμένεται σε κάθε εκτέλεση να έχουν μηδενική τιμή (πριν και μετά την αλλαγή μεγέθους). Οι υπόλοιπες θέσεις του πίνακα, αν και συμβαίνει στο συγκεκριμένο παράδειγμα εκτέλεσης, δεν υπάρχει εγγύηση ότι θα είναι μηδενικές.
Χειρισμός τερματισμού προγραμμάτων Μια άλλη ομάδα συναρτήσεων είναι οι exit(), abort() και atexit() που πραγματοποιούν τερματισμό του προγράμματος και την εκτέλεση ενεργειών πριν τον τερματισμό του. Η συνάρτηση exit() προκαλεί κανονικό τερματισμό του προγράμματος και επιστροφή του ελέγχου στο λειτουργικό σύστημα. Δέχεται ως όρισμα μια ακέραια τιμή, την κατάσταση εξόδου (exit status), που υποδηλώνει την κατάσταση με την οποία τερμάτισε την εκτέλεσή του το πρόγραμμα. Η τιμή αυτή μπορεί να ληφθεί με εντολές που θα αναφερθούν στη συνέχεια σε συστήματα Linux, MacOS ή Windows και μπορούν να χρησιμοποιούνται, για παράδειγμα, σε shell scripts για να καθοδηγήσουν την εκτέλεση μιας ομάδας ενεργειών. Στο ακόλουθο παράδειγμα (κώδικας 17.5) καλείται είτε η exit() είτε η abort().
Ακολουθούν αποτελέσματα 3 εκτελέσεων του κώδικα. Σε κάθε περίπτωση, η κατάσταση εξόδου του προγράμματος (αν έχει τεθεί) λαμβάνεται με την εντολή echo $? σε Linux ή σε MacOS. Αν η έξοδος έχει γίνει με την exit() ή την return() τότε η κατάσταση εξόδου είναι η τιμή του ορίσματος που έχει περάσει στη συνάρτηση. Αν η έξοδος γίνεται με το abort(), τότε δεν λαμβάνεται πίσω κάποια τιμή με νόημα ως κατάσταση εξόδου του προγράμματος (για παράδειγμα, η συγκεκριμένη εκτέλεση επιστρέφει την τιμή 134).
$ ./ch17_p5
How do you want to stop the program (1=exit, 2=abort, 3=return)? 1
Let's set the exit status to 99.
$ echo $?
99
$ ./ch17_p5
How do you want to stop the program (1=exit, 2=abort, 3=return)? 2
Abort.
zsh: abort ch17_p5
$ echo $?
134
$ ./ch17_p5
How do you want to stop the program (1=exit, 2=abort, 3=return)? 3
$ echo $?
0
Αν το πρόγραμμα μεταγλωττιστεί και εκτελεστεί σε Windows, τότε η κατάσταση εξόδου λαμβάνεται με την εντολή:
Επιπλέον, η συνάρτηση atexit() μπορεί να χρησιμοποιηθεί έτσι ώστε να εκτελεστεί κάποιος κώδικας όταν το πρόγραμμα τερματίσει κανονικά την εκτέλεσή του. Η συνάρτηση atexit() δέχεται ως όρισμα μια συνάρτηση που θα εκτελεστεί όταν το πρόγραμμα τερματιστεί επιτυχώς και συνήθως χρησιμοποιείται για αποδέσμευση πόρων και εμφάνιση μηνυμάτων. Στον κώδικα 17.6 παρουσιάζεται ένα παράδειγμα χρήσης της atexit().
Ακολουθεί ένα παράδειγμα εκτέλεσης.
Δημιουργία τυχαίων αριθμών Οι δύο συναρτήσεις του stdlib.h που επιτρέπουν τη δημιουργία ψευδοτυχαίων τιμών είναι η srand() και η rand(). H srand(), δέχεται ως όρισμα το λεγόμενο seed (σπόρος). Αν παραλειφθεί, χρησιμοποιείται ως seed η τιμή 1. Αν εκτελεστεί μια διαδικασία παραγωγής τυχαίων αριθμών και χρησιμοποιηθεί το ίδιο seed, τότε οι ψευδοτυχαίες τιμές που θα εμφανιστούν θα είναι οι ίδιες με την προηγούμενη εκτέλεση. Για να λαμβάνονται διαφορετικές τιμές σε κάθε εκτέλεση, συνήθως χρησιμοποιείται η τιμή που επιστρέφει η κλήση time(NULL) που όπως θα δούμε και στη συνέχεια του ίδιου κεφαλαίου, επιστρέφει το πλήθος δευτερολέπτων της χρονικής στιγμής που πραγματοποιείται η εκτέλεση από την ημερομηνία και ώρα 1/1/1970 00:00:00. Η rand() επιστρέφει μια τυχαία ακέραια τιμή από 0 μέχρι RAND_MAX. H RAND_MAX ορίζεται στη stdlib.h και το πρότυπο της γλώσσας αναφέρει ότι θα πρέπει να έχει ελάχιστη τιμή 215 − 1 = 32767. Ωστόσο, σε πολλές σύγχρονες υλοποιήσεις η τιμή του RAND_MAX είναι η μέγιστη τιμή που μπορεί να λάβει ένας προσημασμένος ακέραιος 32-bit, που είναι 231 − 1 = 2147483647. Στον κώδικα 17.7 παρουσιάζεται ένα παράδειγμα δημιουργίας 10 ακεραίων τιμών, επί δύο φορές, με κάθε ακέραια τιμή να βρίσκεται στο διάστημα από 1 έως και 10.
Ακολουθούν τα αποτελέσματα που παράγονται από δύο εκτελέσεις του κώδικα. Η πρώτη εκτέλεση εμφανίζει:
Η δεύτερη εκτέλεση εμφανίζει:
Η πρώτη γραμμή της εξόδου είναι ίδια και στις δύο περιπτώσεις (διότι έχει χρησιμοποιηθεί το ίδιο seed), ενώ η δεύτερη διαφέρει.
Ένα μειονέκτημα της ανάθεσης τιμής στο seed με το time(NULL) είναι ότι η τιμή που επιστρέφει το time(NULL) αλλάζει σχετικά αργά (κάθε δευτερόλεπτο), οπότε αν το πρόγραμμα εκτελείται ξανά σε διάστημα μικρότερο του ενός δευτερολέπτου, οι τυχαίες τιμές θα είναι οι ίδιες. Για να αποφευχθεί αυτή η συμπεριφορά μπορεί να χρησιμοποιηθεί η συνάρτηση gettimeofday() από το sys/time.h που επιστρέφει το χρονικό διάστημα από 1/1/1970 00:00:00 μέχρι την τρέχουσα χρονική στιγμή με ακρίβεια μικροδευτερολέπτων. Ο κώδικας 17.8 παρουσιάζει την παραγωγή 5 ομάδων πραγματικών τυχαίων αριθμών στο διάστημα από 0 μέχρι 1, χρησιμοποιώντας τη συνάρτηση gettimeofday().
Ακολουθεί ένα παράδειγμα εκτέλεσης:
SEED = 774255
0.0596 0.7902 0.1987 0.1579 0.6974 0.2448 0.5293 0.4014 0.6387 0.6481
0.4010 0.7232 0.0741 0.6294 0.9382 0.0232 0.7664 0.3592 0.4847 0.9366
0.6032 0.2064 0.1106 0.1082 0.9458 0.2407 0.2765 0.0262 0.5820 0.3237
0.4546 0.6432 0.5426 0.6371 0.0795 0.9908 0.5744 0.6008 0.6509 0.2925
0.0924 0.4719 0.4286 0.5220 0.5141 0.4083 0.3387 0.9178 0.0553 0.7129
Αλληλεπίδραση με το λειτουργικό σύστημα Η συνάρτηση system() επιτρέπει την εκτέλεση προγραμμάτων που θα μπορούσαν να εκτελεστούν από τη γραμμή εντολών. Ο κώδικας 17.9 είναι ένα παράδειγμα που ανάλογα με το είδος του λειτουργικού συστήματος (Linux, Windows, MacOS), εκτελείται μια εντολή που εμφανίζει πληροφορίες για το υλικό του συστήματος.
Η ακόλουθη έξοδος είναι τα αποτέλεσμα της εκτέλεσης σε ένα σύστημα MacOS:
System Information:
Hardware:
Hardware Overview:
Model Name: Mac mini
Model Identifier: Mac14 ,12
Model Number: MNH73LL/A
Chip: Apple M2 Pro
Total Number of Cores: 10 (6 performance and 4 efficiency)
Memory: 16 GB
System Firmware Version: 10151.1.1
OS Loader Version: 10151.1.1
Serial Number (system): XXXXXXXXX
Hardware UUID: XXXX‐XXXX‐XXXX‐XXXX‐XXXXXXXXXXX
Provisioning UDID: XXXXXXXX ‐XXXXXXXXXXXX
Activation Lock Status: Enabled
Συνάρτηση γρήγορης ταξινόμησης και συνάρτηση δυαδικής αναζήτησης Οι συναρτήσεις qsort() και bsearch() πραγματοποιούν ταξινόμηση και αναζήτηση αντίστοιχα. Η qsort() υλοποιεί τον αλγόριθμο γρήγορης ταξινόμησης και μπορεί να χρησιμοποιηθεί για να ταξινομήσει αριθμούς, αλφαριθμητικά, και σύνθετες δομές. Έχει τέσσερις παραμέτρους: έναν δείκτη προς τον πίνακα που θα ταξινομηθεί, το πλήθος των στοιχείων του πίνακα, το μέγεθος κάθε στοιχείου σε bytes και μια συνάρτηση σύγκρισης. Η συνάρτηση σύγκρισης καλείται από την qsort() κάθε φορά που πρέπει να συγκριθούν δύο στοιχεία. Αν επιστρέψει αρνητική τιμή σημαίνει ότι το πρώτο στοιχείο είναι μικρότερο του δεύτερου, αν επιστρέψει μηδέν σημαίνει ότι είναι ίσα και αν επιστρέψει θετική τιμή σημαίνει ότι το πρώτο στοιχείο είναι μεγαλύτερο του δεύτερου. Συνεπώς, ο προγραμματιστής πρέπει να γράψει την κατάλληλη συνάρτηση σύγκρισης και να την περάσει ως τέταρτο όρισμα στην qsort() κατά την κλήση της. Η συνάρτηση bsearch() υλοποιεί τον αλγόριθμο δυαδικής αναζήτησης που εντοπίζει τη θέση μιας τιμής σε έναν πίνακα. Πρόκειται για έναν ταχύτατο αλγόριθμο, αλλά απαιτεί ο πίνακας στον οποίο γίνεται η αναζήτηση να είναι διατεταγμένος σε αύξουσα σειρά. Η bsearch() έχει πέντε παραμέτρους: την τιμή αναζήτησης, έναν δείκτη προς τον πίνακα στον οποίο θα γίνει αναζήτηση, το πλήθος των στοιχείων του πίνακα, το μέγεθος κάθε στοιχείου σε bytes και μια συνάρτηση σύγκρισης. Στο παράδειγμα του κώδικα 17.10, χρησιμοποιούνται και οι δύο συναρτήσεις. Αρχικά, ένας πίνακας ταξινομείται σε αύξουσα σειρά και στη συνέχεια ζητείται για μια συγκεκριμένη τιμή εάν υπάρχει στον πίνακα. Η συνάρτηση σύγκρισης cmp() που περνά ως τελευταίο όρισμα και στις δύο συναρτήσεις, δέχεται με τη σειρά της ως ορίσματα δείκτες προς ακεραίους, οπότε στο σώμα της πραγματοποιείται αποαναφορά και στους δύο δείκτες για να επιστραφεί το σωστό αποτέλεσμα.
Το αποτέλεσμα της εκτέλεσης του προγράμματος θα είναι:
17.1.3 string.h
Στο κεφάλαιο 8, στην παράγραφο 8.6, παρουσιάστηκαν 4 βασικές συναρτήσεις που ορίζονται στο string.h, η strlen() για υπολογισμό του μήκους ενός αλφαριθμητικού, η strcat() για συνένωση αλφαριθμητικών, η strcmp() για σύγκριση αλφαριθμητικών και η strtok() για διαχωρισμό ενός αλφαριθμητικού σε τμήματα. Για τις strcat(), strcmp() καθώς και για τη strcpy() που πραγματοποιεί αντιγραφή ενός αλφαριθμητικού σε ένα άλλο, υπάρχουν άλλες εκδόσεις τους που το όνομά τους σχηματίζεται παρεμβάλλοντας το γράμμα n μετά τα τρία πρώτα γράμματα (str) των συναρτήσεων. Έτσι, προκύπτουν οι συναρτήσεις strncat(), strncmp(), strncpy() που δέχονται ένα επιπλέον όρισμα n και εφαρμόζουν την αντίστοιχη ενέργεια στους n πρώτους χαρακτήρες μόνο. Οι συναρτήσεις αυτές θεωρούνται ασφαλείς, καθώς δεν επιτρέπουν την υπερχείλιση των αλφαριθμητικών που δέχονται ως όρισμα εξόδου. Στον κώδικα 17.11, παρουσιάζεται ένα παράδειγμα χρήσης των συναρτήσεων που αναφέρθηκαν.
Η κλήση της συνάρτησης strncpy() στη γραμμή 10 αντιγράφει τους 22 πρώτους χαρακτήρες από το αλφαριθμητικό source στο αλφαριθμητικό result, προσθέτοντας και τον χαρακτήρα '\0' στη θέση 23. Στη συνέχεια, δύο διαδοχικές κλήσεις της strncat() προσθέτουν στο τέλος του αλφαριθμητικού result τμήματα του αλφαριθμητικού source. Τέλος, για κάθε συνδυασμό ανά δύο των 3 αλφαριθμητικών της γραμμής 20, πραγματοποιείται σύγκριση εξετάζοντας μόνο τους 2 πρώτους χαρακτήρες κάθε αλφαριθμητικού και εμφανίζεται η λεξικογραφική σειρά μεταξύ τους. Η έξοδος του προγράμματος είναι:
Original=Controlling complexity is the essence of computer programming.
Partially copied=Controlling complexity
Concatenated=Controlling complexity is programming.
Comparisons using the first 2 letters of each string:
ABCD and ABCD are equal.
ABCD and ABEF are equal.
ABCD is less than BCDE.
ABEF and ABCD are equal.
ABEF and ABEF are equal.
ABEF is less than BCDE.
BCDE is greater than ABCD.
BCDE is greater than ABEF.
BCDE and BCDE are equal.
Η συνάρτηση strdup() Οι strdup() και strndup() είναι δύο συναρτήσεις που πρόσφατα προστέθηκαν στο string.h (με το πρότυπο C23), αν και υποστηρίζονται από τους διαδεδομένους μεταγλωττιστές της C εδώ και πολλά χρόνια. Η strdup() επιστρέφει έναν δείκτη σε ένα αλφαριθμητικό που τερματίζεται με '\0', που είναι αντίγραφο του αλφαριθμητικού που δέχεται ως όρισμα. Καθώς ο δείκτης που επιστρέφεται δείχνει προς θέσεις μνήμης που έχουν δεσμευθεί στον σωρό, θα πρέπει να ακολουθεί εντολή απελευθέρωσης της μνήμης, όταν πλέον δεν χρησιμοποιείται. Στον κώδικα 17.12 παρουσιάζεται ένα παράδειγμα χρήσης των συναρτήσεων strdup() και strndup(). Στη γραμμή 8 ορίζεται μια τοπική μεταβλητή αλφαριθμητικού που παύει να υπάρχει στη γραμμή 12. H strdup() δημιουργεί ένα αντίγραφο των περιεχομένων της t στον σωρό και επιστρέφει έναν δείκτη που αποθηκεύεται στο s1. Παρόμοια, η stdndup() δημιουργεί αντίγραφο μόνο των 25 πρώτων χαρακτήρων του t στον σωρό. Η συνάρτηση strcpy() αντιγράφει το t στον πίνακα s3 που διατηρείται στη στοίβα. Οι γραμμές 16, 17, με δύο κλήσεις της free(), απελευθερώνουν τη μνήμη που έχει δεσμευθεί έμμεσα από τις strdup() και strndup().
Ακολουθεί η έξοδος της εκτέλεσης του κώδικα. Οι διευθύνσεις που εμφανίζονται, και αφορούν τα αλφαριθμητικά s1, s2 και s3, υποδηλώνουν ότι τα s1 και s2 βρίσκονται σε διαφορετική περιοχή μνήμης από το s3 (σωρό και στοίβα αντίστοιχα).
string at 0x130606b10: First, solve the problem. Then, write the code.
string at 0x130606a70: First, solve the problem.
string at 0x16ce9ae18: First, solve the problem. Then, write the code.
Συναρτήσεις χειρισμού μπλοκ μνήμης Μια χρήσιμη ομάδα συναρτήσεων του string.h, που χειρίζονται μπλοκ μνήμης αντί για αλφαριθμητικά, αποτελείται από συναρτήσεις όπως οι memset(), memcmp() και memcpy(). Στον κώδικα 17.13 δίνεται ένα παράδειγμα χρήσης αυτών των συναρτήσεων.
Ακολουθεί το αποτέλεσμα της εκτέλεσης του κώδικα:
After memset, the destination is:
*******************************************************************************
After memcpy, the destination is:
**************Never trust a computer you can't throw out a window**************
BAD and BAD are equal.
BAD is greater than AD.
BAD is less than D.
Η διαφορά των συναρτήσεων memcpy() και strcpy() είναι ότι ενώ η memcpy() αντιγράφει ένα πλήθος bytes από ένα μπλοκ μνήμης σε ένα άλλο, η strcpy() αντιγράφει τα περιεχόμενα ενός αλφαριθμητικού σε ένα άλλο αλφαριθμητικό. Έτσι, ενώ η memcpy() εφαρμόζεται σε μπλοκ μνήμης, η strcpy() εφαρμόζεται σε αλφαριθμητικά.
Το string.h ορίζει και τη συνάρτηση memmove( που έχει παρόμοια λειτουργικότητα με τη memcpy() καθώς και οι δύο αντιγράφουν ένα μπλοκ μνήμης σε ένα άλλο. Ωστόσο, αν τα δύο αυτά μπλοκ μνήμης επικαλύπτονται τότε μόνο η memmove( δίνει εγγυημένα σωστά αποτελέσματα, ενώ η memcpy() παρουσιάζει ακαθόριστη συμπεριφορά (undefined behavior).
17.1.4 math.h
Το math.h περιέχει τριγωνομετρικές συναρτήσεις όπως οι sin(), cos(), tan(), λογαριθμικές συναρτήσεις όπως οι log(), log10(), και άλλες μαθηματικές συναρτήσεις όπως η sqrt(), η cbrt() και η pow() για υπολογισμό τετραγωνικής ρίζας, κυβικής ρίζας και ύψωσης σε δύναμη αντίστοιχα. Στη συνέχεια θα παρουσιαστούν ορισμένες συναρτήσεις του math.h ξεκινώντας με τις συναρτήσεις ceil(), floor(), round() και trunc(). Η συνάρτηση ceil() δέχεται ως παράμετρο μια πραγματική τιμή x και επιστρέφει τον μικρότερο ακέραιο αριθμό που είναι μεγαλύτερος ή ίσος του x. Αντίστοιχα, η floor() επιστρέφει τον μεγαλύτερο ακέραιο που είναι μικρότερος ή ίσος του x. Η round() κάνει στρογγυλοποίηση, ενώ η trunc() αφαιρεί (truncate) το κλασματικό μέρος. Ο πίνακας 17.1 συνοψίζει τη συμπεριφορά των συναρτήσεων.
Συνάρτηση | Λειτουργία | Αποτέλεσμα για θετικό x | Αποτέλεσμα για αρνητικό x | Παραδείγματα |
---|---|---|---|---|
ceil(x) | Στρογγυλοποίηση προς τα πάνω, στον πλησιέστερο ακέραιο | Μικρότερος ακέραιος που είναι μεγαλύτερος ή ίσος του x | Διατήρηση του ακέραιου μέρους | ceil(2.3)=3.0, ceil(-2.3)=-2.0 |
floor(x) | Στρογγυλοποίηση προς τα κάτω, στον πλησιέστερο ακέραιο | Μεγαλύτερος ακέραιος που είναι μικρότερος ή ίσος του x | Μεγαλύτερος ακέραιος που είναι μικρότερος ή ίσος του x | floor(2.3)=2.0, floor(-2.3)=-3.0 |
trunc(x) | Αφαίρεση του δεκαδικού μέρους | Αφαίρεση του δεκαδικού μέρους του x | Αφαίρεση του δεκαδικού μέρους του x | trunc(2.3)=2.0, trunc(-2.3)=-2.0 |
round(x) | Στρογγυλοποίηση προς τον πλησιέστερο ακέραιο | Αν το κλασματικό μέρος < 0.5 τότε προς τα κάτω, αλλιώς προς τα πάνω | Αν το κλασματικό μέρος < 0.5, στρογγυλοποίηση προς τα πάνω, αλλιώς προς τα κάτω | round(2.3)=2.0, round(2.7)=3.0, round(-2.3)=-2.0, round(-2.7)=-3.0 |
Σχετική είναι και η συνάρτηση modf() που χωρίζει μια πραγματική τιμή σε ακέραιο και δεκαδικό μέρος. Το ακόλουθο πρόγραμμα (κώδικας 17.14) δείχνει τη χρήση αυτών των συναρτήσεων.
Ακολουθεί η έξοδος του προγράμματος.
x floor ceiling round trunc integer_part fractional_part
2.3 2.0 3.0 2.0 2.0 2.0 0.3
‐2.3 ‐3.0 ‐2.0 ‐2.0 ‐2.0 ‐2.0 ‐0.3
2.7 2.0 3.0 3.0 2.0 2.0 0.7
‐2.7 ‐3.0 ‐2.0 ‐3.0 ‐2.0 ‐2.0 ‐0.7
Μια άλλη συνάρτηση του math.h είναι η fmod(), που υπολογίζει το υπόλοιπο της διαίρεσης δύο αριθμών κινητής υποδιαστολής. Ειδικότερα, όπως αναφέρεται στην τεκμηρίωση της συνάρτησης(1), η fmod(x, y) επιστρέφει την τιμή 𝑥 − 𝑖 ∗ 𝑦 για κάποιον ακέραιο 𝑖τέτοιο ώστε, αν το 𝑦 δεν είναι μηδενικό, το αποτέλεσμα να έχει το ίδιο πρόσημο με το 𝑥 και τιμή μικρότερη ή ίση της τιμής του 𝑦. Υπενθυμίζεται ότι η ακέραια διαίρεση και το ακέραιο υπόλοιπο της διαίρεσης, υπολογίζονται μέσω των τελεστών / και %, όταν οι τελεστέοι είναι ακέραιοι. Στο ακόλουθο παράδειγμα (κώδικας 17.15) φαίνεται η χρήση της fmod().
- Η εμφάνιση της περιγραφής της λειτουργίας της συνάρτησης fmod() γίνεται με την εντολή “man fmod” στη γραμμή εντολών σε συστήματα Linux ή MacOS.
Κώδικας 17.15: ch17_p15.c - υπόλοιπο διαίρεσης δύο πραγματικών αριθμών για διαίρεση με ακέραιο πηλίκο. | |
---|---|
Το πρόγραμμα κατά την εκτέλεσή του θα εμφανίσει:
Οι τριγωνομετρικές συναρτήσεις του math.h αποτελούν μια ακόμη κατηγορία συναρτήσεών του. Πέρα από τις συναρτήσεις sin(), cos(), tan() για ημίτονο, συνημίτονο και εφαπτομένη, που αναφέρθηκαν ήδη, υπάρχουν οι συναρτήσεις asin(), acos(), atan() και atan2(), που υπολογίζουν τόξα τριγωνομετρικών συναρτήσεων. Οι τριγωνομετρικές συναρτήσεις δέχονται ως ορίσματα γωνίες κύκλων σε ακτίνια (radians), οπότε αν οι διαθέσιμες τιμές είναι σε μοίρες (degrees) τότε θα πρέπει πρώτα να μετατραπούν. Η μετατροπή γίνεται με βάση την εξίσωση 1𝜋 = 180∘, από όπου προκύπτει ότι η μετατροπή μοιρών σε ακτίνια γίνεται πολλαπλασιάζοντας τις μοίρες επί 0.0174532925, ενώ η μετατροπή ακτινίων σε μοίρες γίνεται πολλαπλασιάζοντας τα ακτίνια επί 57.2957795. Ο κώδικας 17.16 είναι ένα παράδειγμα χρήσης τριγωνομετρικών συναρτήσεων.
Ακολουθεί η έξοδος του προγράμματος:
3.141593(rad)=180(deg): sin=+0.000000, cos=‐1.000000, tan=‐0.000000
1.570796(rad)= 90(deg): sin=+1.000000, cos=+0.000000, tan=+16331239353195370.000000
1.047198(rad)= 60(deg): sin=+0.866025, cos=+0.500000, tan=+1.732051
0.785398(rad)= 45(deg): sin=+0.707107, cos=+0.707107, tan=+1.000000
0.523599(rad)= 30(deg): sin=+0.500000, cos=+0.866025, tan=+0.577350
Στη γραμμή 5, η τιμή του 𝜋 λαμβάνεται με το τόξο συνημιτόνου του -1. Η κλήση acos(‐1) επιστρέφει σε ακτίνια τη γωνία που το συνημίτονό της είναι -1, δηλαδή τη γωνία 𝜋. Η τιμή 𝜋 θα μπορούσε επίσης να ληφθεί με την έκφραση 4.0 * atan(1.0), καθώς η εφαπτομένη των 𝜋/4 = 45∘ είναι 1.0, ή με τη σταθερά M_PI της math.h (προστέθηκε με το πρότυπο C99). Ένα ακόμα σημείο με ενδιαφέρον στα αποτελέσματα είναι ότι η τιμή της εφαπτομένης που εμφανίζεται για τις 𝜋/2 = 90∘ είναι μια πολύ μεγάλη τιμή. Τυπικά, θα έπρεπε να υπήρχε κάποιος έλεγχος ξεχωριστά για την περίπτωση αυτή και όχι να εμφανίζεται μια αυθαίρετα μεγάλη τιμή. Ας σημειωθεί ότι καθώς η γωνία 𝜋/2 προσεγγίζεται από αριστερά (δηλαδή από μικρότερες τιμές), η τιμή της εφαπτομένης προσεγγίζει το -∞, ενώ όταν αυτό συμβαίνει από δεξιά (δηλαδή από μεγαλύτερες τιμές) η τιμή της εφαπτομένης προσεγγίζει το +∞. Αυτό φαίνεται στον κώδικα 17.17.
Ακολουθούν τα αποτελέσματα εκτέλεσης του κώδικα:
1.470796(rad)=84.270422(deg): tan=+9.96664
1.560796(rad)=89.427042(deg): tan=+99.99667
1.569796(rad)=89.942704(deg): tan=+999.99967
1.570696(rad)=89.994270(deg): tan=+9999.99997
1.570786(rad)=89.999427(deg): tan=+100000.00000
1.570806(rad)=90.000573(deg): tan=‐100000.00000
1.570896(rad)=90.005730(deg): tan=‐9999.99997
1.571796(rad)=90.057296(deg): tan=‐999.99967
1.580796(rad)=90.572958(deg): tan=‐99.99667
1.670796(rad)=95.729578(deg): tan=‐9.96664
Οι τριγωνομετρικές συναρτήσεις τυπικά δέχονται ορίσματα double και επιστρέφουν τιμές double. Από το πρότυπο C99 και μετά υπάρχουν οι τριγωνομετρικές συναρτήσεις με ονόματα που τελειώνουν σε f και σε l, όπως για παράδειγμα οι sinf(), sinl(), που δέχονται ως ορίσματα, αλλά και επιστρέφουν float και long double τιμές αντίστοιχα, αντί για double. Επίσης, με το πρότυπο C99 προστέθηκε το tgmath.h που ορίζει μακροεντολές όπως η sin που παρέχουν γενερική (generic) πρόσβαση στις τριγωνομετρικές συναρτήσεις, καλώντας την κατάλληλη συνάρτηση ανάλογα με τον τύπο των ορισμάτων. Ο κώδικας 17.18 αποτελεί ένα παράδειγμα χρήσης αυτών των συναρτήσεων.
Η έξοδος του προγράμματος είναι η ακόλουθη:
sinf(1.137169242) using macro=0.907447755
sin(1.13716927413690527) using macro=0.90744776169021168
sinl(1.137169274136905272599) using macro=0.907447761690211684993
Στις γραμμές 14-17 εκτελείται τρεις φορές η μακροεντολή sin και με βάση το όρισμα που δέχεται, καλεί την κατάλληλη συνάρτηση ανάμεσα στις sinf(), sin() και sinl().
17.1.5 time.h
Το αρχείο επικεφαλίδας time.h ορίζει διάφορους τύπους δεδομένων σχετικούς με τον χρόνο, όπως τον time_t, τον clock_t και τον struct tm, που θα παρουσιαστούν στη συνέχεια μαζί με συναρτήσεις που τους χρησιμοποιούν και παραδείγματα.
Ο τύπος δεδομένων time_t σε συστήματα που υποστηρίζουν το POSIX, είναι ένας ακέραιος τύπος με εύρος τιμών ικανό να αναπαραστήσει το πλήθος δευτερολέπτων που έχουν παρέλθει από το λεγόμενο epoch που είναι η 1η Ιανουαρίου του 1970 στις 00:00:00 UTC (Coordinated Universal Time). Η συνάρτηση time() μπορεί να χρησιμοποιηθεί για να επιστραφεί το πλήθος των δευτερολέπτων της τρέχουσας χρονικής στιγμής από το epoch, ενώ οι συναρτήσεις difftime() και ctime() χρησιμοποιούνται για τον υπολογισμό της απόστασης σε δευτερόλεπτα ανάμεσα σε δύο τιμές τύπου time_t και για την εμφάνιση τιμών τύπου time_t ως αλφαριθμητικά χρονικών στιγμών (ημερομηνία και ώρα) αντίστοιχα. Στο ακόλουθο παράδειγμα (κώδικας 17.19) χρησιμοποιούνται οι συναρτήσεις που αναφέρθηκαν και η συνάρτηση sleep() που προκαλεί παύση της εκτέλεσης για ένα διάστημα που δίνεται σε δευτερόλεπτα. Η συνάρτηση sleep() ορίζεται στο αρχείο επικεφαλίδας unistd.h.
Ακολουθεί ένα αποτέλεσμα εκτέλεσης:
Ο τύπος clock_t μπορεί να χρησιμοποιηθεί για να αποθηκεύσει μια τιμή ανάλογη του πλήθους των χτύπων (ticks) του ρολογιού από την αρχή εκτέλεσης του προγράμματος μέχρι το σημείο όπου καλείται η συνάρτηση clock(). Ως χτύπος ρολογιού μπορεί να θεωρηθεί ότι είναι ο χρόνος που απαιτείται για να εκτελεστεί μια μικροεντολή στην ΚΜΕ (Κεντρική Μονάδα Επεξεργασίας). Για να μετατραπεί η τιμή που επιστρέφει η clock() σε χρόνο διαιρείται με τη σταθερά CLOCKS_PER_SEC, οπότε επιστρέφεται μια δεκαδική τιμή που αναπαριστά δευτερόλεπτα. Ο τύπος clock_t χρησιμοποιείται συχνά για να μετρήσει τον χρόνο εκτέλεσης κώδικα ανάμεσα σε δύο σημεία ενδιαφέροντος στον κώδικα. Ένα σχετικό παράδειγμα παρουσιάζεται στον κώδικα 17.20 που ακολουθεί:
Ακολουθεί ένα αποτέλεσμα εκτέλεσης. Ας σημειωθεί ότι η σταθερά CLOCKS_PER_SEC έχει την προκαθορισμένη τιμή 1000000, ανεξάρτητα του υλικού του συστήματος που εκτελεί τον κώδικα.
CLOCKS_PER_SEC=1000000, start=3664, end=122949
Result=5000000050000000
Elapsed time=0.119285 seconds
Ένας άλλος τύπος δεδομένων του time.h, που επιτρέπει τον χειρισμό ημερομηνιών και ωρών, είναι η δομή struct tm που παρατίθεται στη συνέχεια:
struct tm {
int tm_sec; // δευτερόλεπτα , 0‐59
int tm_min; // λεπτά, 0‐59
int tm_hour; // ώρες, 0‐23
int tm_mday; // ημέρα μήνα, 1‐31
int tm_mon; // μήνας, 0‐11
int tm_year; // πλήθος ετών από το 1900
int tm_wday; // ημέρα εβδομάδας από την Κυριακή , 0‐6
int tm_yday; // πλήθος ημερών από 1η Ιανουαρίου , 0‐365
int tm_isdst; // > 0 για θερινή ώρα, 0 για χειμερινή , < 0 για μη διαθέσιμη πληροφορία
};
Υπάρχουν διάφορες συναρτήσεις που χρησιμοποιούν το struct tm, όπως η gmtime(), η localtime(), η asctime() και η strftime(). Η gmtime() μετατρέπει μια τιμή time_t σε struct tm, όπως και η locatime() με τη διαφορά ότι η πρώτη κάνει τη μετατροπή σε UTC χρόνο, ενώ η δεύτερη σε τοπικό χρόνο. Ο κώδικας 17.21 αποτελεί ένα παράδειγμα όπου λαμβάνεται η τρέχουσα ημέρα και ώρα κατά την εκτέλεση ως time_t και μετατρέπεται με τη gmtime() σε struct tm. Στη συνέχεια μετατρέπεται με την asctime() η struct tm σε αλφαριθμητικό ημερομηνίας και ώρας προκειμένου να εμφανιστεί. Μετά, προστίθεται 1 ημέρα και 10 ώρες στη δομή και μετατρέπεται σε time_t, πριν τελικά μετατραπεί ξανά με τη gmtime() σε struct tm. Αυτή η διπλή μετατροπή γίνεται προκειμένου να περιοριστούν οι τιμές των επιμέρους πεδίων του struct tm σε έγκυρες τιμές (π.χ. να μην υπάρχει τιμή μεγαλύτερη του 23 για τις ώρες). Τέλος, χρησιμοποιείται η συνάρτηση strftime() για να τοποθετηθεί σε έναν πίνακα χαρακτήρων το αλφαριθμητικό εξόδου με την ημερομηνία και ώρα. Η strftime() χρησιμοποιεί ως τρίτο στη σειρά όρισμα ένα αλφαριθμητικό που καθορίζει με ειδικούς κωδικούς μορφοποίησης(1) τη μορφή που θα έχει το αποτέλεσμα.
Ακολουθεί ένα αποτέλεσμα εκτέλεσης. Το ίδιο παράδειγμα θα μπορούσε να χρησιμοποιεί την τοπική ώρα αν όλες οι κλήσεις της gmtime() είχαν αντικατασταθεί με κλήσεις της localtime().
17.2 Εξωτερικές βιβλιοθήκες της C
Ένα μεγάλο πλεονέκτημα της C είναι η πληθώρα βιβλιοθηκών που υπάρχουν για την εξυπηρέτηση διαφόρων αναγκών όπως δημιουργία γραφικών διεπαφών (π.χ. GTK+, IUP), προχωρημένες δυνατότητες μαθηματικών (π.χ. GSL, BLAS, GMP), αλληλεπίδραση με βάσεις δεδομένων (π.χ. SQLite), γραφικά (π.χ. SDL, SIGIL), επικοινωνίες (π.χ. libcurl, ZeroMQ) και άλλα. Μια λίστα με εξωτερικές βιβλιοθήκες ανοικτού κώδικα της C μπορεί να εντοπιστεί στο 1. Επίσης, σελίδες τύπου awesome(1) όπως η 2 περιέχουν συνδέσμους προς βιβλιοθήκες και άλλα ενδιαφέροντα θέματα σχετικά με τη C, οργανωμένα σε κατηγορίες.
Στη συνέχεια θα παρουσιαστεί ένα παράδειγμα εγκατάστασης και χρήσης της εξωτερικής βιβλιοθήκης GSL. Υπάρχουν διάφοροι τρόποι για την εγκατάσταση βιβλιοθηκών της C, που εξαρτώνται από το λειτουργικό σύστημα και την ίδια τη βιβλιοθήκη. Για παράδειγμα υπάρχουν βιβλιοθήκες που είναι “header only” που σημαίνει ότι αρκεί να μεταφορτωθεί ένα αρχείο επικεφαλίδας και στη συνέχεια απλά να γίνει include έτσι ώστε να χρησιμοποιηθεί η βιβλιοθήκη στον κώδικα. Ωστόσο, συχνότερα, οι βιβλιοθήκες πρέπει να εγκαθίστανται προκειμένου να χρησιμοποιηθούν. Στο Linux υπάρχουν οι λεγόμενοι διαχειριστές πακέτων (package managers), που επιτρέπουν την εγκατάσταση βιβλιοθηκών και άλλων λογισμικών. Έτσι, η βιβλιοθήκη GSL μπορεί να εγκατασταθεί, για παράδειγμα, σε μια διανομή Ubuntu, με την ακόλουθη εντολή:
Αυτό που συμβαίνει με μια τέτοια εντολή είναι ότι αντιγράφονται στον υπολογιστή όλα τα απαιτούμενα αρχεία της βιβλιοθήκης GSL, και το λειτουργικό σύστημα ενημερώνεται για τις θέσεις των αρχείων επικεφαλίδας και των δυαδικών αρχείων της βιβλιοθήκης. Η εγκατάσταση της βιβλιοθήκης σε MacOS μπορεί να γίνει με παρόμοιο τρόπο, εφόσον πρώτα εγκατασταθεί ένας διαχειριστής πακέτων όπως ο HomeBrew ή ο MacPorts. Στα Windows η εγκατάσταση δεν είναι το ίδιο εύκολη καθώς δεν διατίθεται επίσημος διαχειριστής πακέτων και οι λύσεις που προτείνονται, όπως το chocolatey, προσφέρουν δυνατότητα εγκατάστασης μόνο για κάποιες βιβλιοθήκες και λογισμικά. Για να γεφυρωθούν οι διαφορές στον τρόπο εγκατάστασης των εξωτερικών βιβλιοθηκών ανάμεσα στα διάφορα λειτουργικά συστήματα και τις εκδόσεις τους, μπορεί να χρησιμοποιηθεί το λογισμικό vcpkg (https://github.com/microsoft/vcpkg)
Το vcpkg είναι ένα σύστημα διαχείρισης εγκαταστάσεων βιβλιοθηκών για τη C και τη C++. Έχει αναπτυχθεί από τη Microsoft και επιτρέπει την αυτοματοποίηση της λήψης, της μεταγλώττισης και σύνδεσης (build), καθώς και της εγκατάστασης βιβλιοθηκών C και C++ σε συστήματα Windows, Linux και MacOS. Ο σκοπός του είναι να απλοποιήσει τη διαδικασία ανάπτυξης λογισμικού με αυτοματοποίηση της διαχείρισης εγκατάστασης βιβλιοθηκών.
Οι οδηγίες εγκατάστασης του vcpkg βρίσκονται στο https://github.com/microsoft/vcpkg. Από τη στιγμή που έχει εγκατασταθεί η εγκατάσταση των βιβλιοθηκών γίνεται με εντολές της μορφής:
Για παράδειγμα, η βιβλιοθήκη GSL εγκαθίσταται με την εντολή:
Το vcpkg ανιχνεύει το υπολογιστικό σύστημα στο οποίο εκτελείται και εγκαθιστά την κατάλληλη έκδοση της βιβλιοθήκης. Το vcpkg χρησιμοποιεί τις λεγόμενες τριάδες (triplets) στη διαχείριση των διαφορετικών διαμορφώσεων κατά την εγκατάσταση των βιβλιοθηκών. Κάθε τριάδα αντιπροσωπεύει μια πλατφόρμα, αρχιτεκτονική και πρόσθετες ρυθμίσεις. Για παράδειγμα η τριάδα x64‐linux‐dynamic σημαίνει ότι η αρχιτεκτονική της πλατφόρμας είναι 64 bits, το λειτουργικό σύστημα είναι linux και θα δημιουργηθεί βιβλιοθήκη για δυναμική σύνδεση. Συνεπώς, η βιβλιοθήκη GSL θα μπορούσε να είχε εγκατασταθεί και με την ακόλουθη εντολή:
Για να μπορεί να χρησιμοποιηθεί η βιβλιοθήκη GSL θα πρέπει να οριστούν κατά τη μεταγλώττιση και σύνδεση μέσω των διακοπτών ‐I και ‐L οι κατάλληλες διαδρομές προς τα αρχεία επικεφαλίδας και τα δυαδικά αρχεία της βιβλιοθήκης. Στο παράδειγμα του κώδικα 17.22 χρησιμοποιείται η δυνατότητα της GSL να δημιουργεί όλες τις μεταθέσεις (permutations) των στοιχείων ενός συνόλου.
Η μεταγλώττιση του κώδικα θα γίνει με τις ακόλουθες εντολές μεταγλώττισης ανά περίπτωση. Σε Windows, υποθέτοντας ότι ο χρήστης του συστήματος έχει όνομα a_user και ότι το vcpkg έχει εγκατασταθεί στον κατάλογο c:\Users\a_user, η μεταγλώττιση θα πρέπει να γίνει με την ακόλουθη εντολή:
> gcc ch17_p22.c ‐IC:\Users\a_user\vcpkg\installed\x64‐windows\include
↪ ‐LC:\Users\a_user\vcpkg\installed\x64‐windows\lib ‐lgsl ‐lgslcblas ‐lm
Στα Windows θα πρέπει να τοποθετηθούν τα αρχεία gsl.dll και gslcblas.dll στον ίδιο κατάλογο με το εκτελέσιμο. Τα αρχεία αυτά έχουν δημιουργηθεί κατά την εγκατάσταση και βρίσκονται στη διαδρομή C:\Users\a_user\vcpkg\installed\x64‐windows\bin. Εναλλακτικά, αυτή η διαδρομή μπορεί να προστεθεί στο PATH, οπότε δεν θα απαιτούνταν η αντιγραφή των αρχείων gsl.dll και gslcblas.dll.
Σε Linux υποθέτοντας ότι ο χρήστης του συστήματος έχει όνομα a_user και ότι το vcpkg έχει εγκατασταθεί στον γονικό κατάλογο (/home/a_user), η μεταγλώττιση θα πρέπει να γίνει με την ακόλουθη εντολή:
$ gcc ch17_p22.c ‐I/home/a_user/vcpkg/installed/x64‐linux/include
↪ ‐L/home/a_user/vcpkg/installed/x64‐linux/lib ‐lgsl ‐lgslcblas ‐lm
Σε MacOS (αρχιτεκτονικής arm64) υποθέτοντας ότι ο χρήστης του συστήματος έχει όνομα a_user και ότι το vcpkg έχει εγκατασταθεί στον γονικό κατάλογο (/Users/a_user), η μεταγλώττιση θα πρέπει να γίνει με την ακόλουθη εντολή:
$ gcc ch17_p22.c ‐I/Users/a_user/vcpkg/installed/arm64‐osx/include/
↪ ‐L/Users/a_user/vcpkg/installed/arm64‐osx/lib ‐lgsl ‐lgslcblas ‐lm
Τα αποτελέσματα εκτέλεσης θα είναι και στις 3 περιπτώσεις τα ακόλουθα:
Alice Bob Charlie
Alice Charlie Bob
Bob Alice Charlie
Bob Charlie Alice
Charlie Alice Bob
Charlie Bob Alice
17.3 Ασκήσεις
Άσκηση 1
Γράψτε ένα πρόγραμμα που ο χρήστης θα προσπαθεί να εντοπίσει, με τον μικρότερο αριθμό προσπαθειών, έναν ακέραιο αριθμό, που θα έχει δημιουργηθεί τυχαία σε ένα εύρος τιμών (π.χ. 1 έως 100). Το πρόγραμμα θα ζητά από τον χρήστη να μαντέψει τον αριθμό και σε περίπτωση αποτυχίας θα εμφανίζει μήνυμα σχετικά με το εάν ο αριθμός που επιλέχθηκε είναι μικρότερος ή μεγαλύτερος από τον αριθμό που αναζητείται. Όταν ο χρήστης πετύχει τον αριθμό, το πρόγραμμα θα σταματά και θα εμφανίζει το πλήθος των προσπαθειών που χρειάστηκαν για τον εντοπισμό του.
Άσκηση 2
Γράψτε ένα πρόγραμμα που να ζητά από τον χρήστη να εισάγει δύο ημερομηνίες, σε μορφή έτους, μήνα, ημέρας. Το πρόγραμμα να μετατρέπει τις ημερομηνίες σε time_t, να υπολογίζει τη διαφορά τους σε ημέρες και να την εμφανίζει.
Άσκηση 3
Γράψτε ένα πρόγραμμα που να υπολογίζει χρησιμοποιώντας επανάληψη πόσες φορές χρειάζεται να υποδιπλασιαστεί μια ποσότητα έτσι ώστε να γίνει ίση ή μικρότερη από μια άλλη ποσότητα. Υπολογίστε την ίδια τιμή με χρήση λογαριθμικής συνάρτησης από το math.h.
Άσκηση 4
Δημιουργήστε δύο ίσου μεγέθους πίνακες με τυχαίες τιμές, στο εύρος τιμών από 1 μέχρι 100, που ο χρήστης θα δίνει το πλήθος των στοιχείων τους. Χρησιμοποιήστε τη βιβλιοθήκη GSL για να επιτύχετε τον υπολογισμό του εσωτερικού γινομένου των δύο πινάκων.
-
cppreference.com: A list of open source C libraries. https://en.cppreference.com/w/c/links/libs. [Online; accessed 2023-July-12]. ↩
-
Awesome-c: Curated list of awesome lists. https://project-awesome.org/inputsh/awesome-c.[Online; accessed 2023-July-12]. ↩