Σημειώσεις για το Κεφάλαιο 1 - Προκαταρκτικά
1. Εισαγωγή
- Γιατί δεν αρκεί η καλή γνώση 1-2 γλωσσών προγραμματισμού;
- Οι δύο βασικές επιρροές στη σχεδίαση γλωσσών προγραμματισμού:
- Αρχιτεκτονική Η/Υ
- Μεθοδολογίες σχεδίασης προγραμμάτων
1.1 Λόγοι μελέτης εννοιών γλωσσών προγραμματισμού
- Αυξημένη δυνατότητα έκφρασης ιδεών. Για παράδειγμα προγραμματιστής της C που γνωρίζει τη χρήση των λεξικών στην Python μπορεί να σχεδιάσει παρόμοιες δομές ή να αναζητήσει βιβλιοθήκες που υλοποιούν παρόμοιες δομές στη C όπως η GLib - Hash Tables.
- Βελτιωμένο υπόβαθρο για την επιλογή κατάλληλων γλωσσών προγραμματισμού για νέα έργα.
- Ευκολία εκμάθησης νέων γλωσσών προγραμματισμού. Για παράδειγμα η εκμάθηση της Ruby από προγραμματιστές που κατανοούν τις βασικές αρχές του αντικειμενοστραφούς προγραμματισμού καθίσταται ευκολότερη καθώς η Ruby είναι αντικειμενοστραφής.
- Η δημοφιλία των γλωσσών προγραμματισμού παρουσιάζει σημαντικές διακυμάνσεις
- Γνώση λεξιλογίου που χρησιμοποιείται σε γλώσσες προγραμματισμού και μεταγλωττιστές, π.χ. dynamic binding (δυναμική πρόσδεση), scope (εμβέλεια), υπερφόρτωση κ.α.
- Καλύτερη κατανόηση της σημασίας υλοποίησης. Για παράδειγμα η γνώση του τρόπου με τον οποίο πραγματοποιείται η κλήση υποπρογραμμάτων μπορεί να εξηγήσει την ενδεχόμενη αργή εκτέλεση που δημιουργεί η κλήση ενός μικρού υποπρογράμματος πολλές φορές.
- Καλύτερη χρήση γλωσσών που ήδη γνωρίζουμε.
- Αναβάθμιση γνώσεων πληροφορικής και ανάπτυξη ικανότητας επιχειρηματολογίας για λόγους επικράτησης συγκεκριμένων γλωσσών προγραμματισμού έναντι άλλων, π.χ. FORTRAN vs. ALGOL.
1.2 Τομείς προγραμματισμού
Οι Η/Υ χρησιμοποιούνται σε πάρα πολλούς και διαφορετικούς μεταξύ τους κλάδους όπως οι επιστημονικές εφαρμογές, οι επιχειρηματικές εφαρμογές, οι εφαρμογές τεχνητής νοημοσύνης, οι εφαρμογές για τον παγκόσμιο ιστό, οι τηλεπικοινωνίες, τα βιντεοπαιχνίδια κ.α. Συνεπώς, η ανάπτυξη γλωσσών προγραμματισμού με διαφορετικούς στόχους σχεδίασης είναι μια πραγματικότητα.
- Επιστημονικές εφαρμογές. Η αρχική απαίτηση ήταν για πράξεις με δεκαδικούς αριθμούς κινητής υποδιαστολής και επεξεργασία δεδομένων σε διανύσματα και δισδιάστατους πίνακες. Στο παρελθόν κυρίαρχη γλώσσα για επιστημονικές εφαρμογές ήταν η FORTRAN η οποία έπρεπε να ανταγωνισθεί σε ταχύτητα την ASSEMBLY. Σήμερα χρησιμοποιούνται για επιστημονικές εφαρμογές πολλές γλώσσες προγραμματισμού με σημαντικότερες τις FORTRAN, C, C++, Python, R, Java, MATLAB, Julia κ.α.
- Επιχειρηματικές εφαρμογές. Καλύπτουν απαιτήσεις παραγωγής αναφορών (reports), επεξεργασίας δεκαδικών αριθμών και αλφαριθμητικών.
- COBOL (1960)
- Τεχνητή νοημοσύνη. Δίνεται έμφαση σε συμβολικές και όχι σε αριθμητικές πράξεις, δηλαδή πράξεις πάνω σε σύμβολα που διατηρούνται σε συνδεδεμένες δομές δεδομένων.
- Lisp (1959)
- Prolog (1970)
- Python, C, C++, Java κ.α.
- Παγκόσμιος ιστός.
- HTML (markup language) + CSS
- Ενσωμάτωση κώδικα (JavaScript, PHP) σε έγγραφα HTML
1.3 Κριτήρια αποτίμησης γλωσσών προγραμματισμού
Κριτήρια:
- Ευκολία ανάγνωσης (readability)
- Ευκολία συγγραφής προγραμμάτων
- Αξιοπιστία
- Κόστος
- Κόστος εκπαίδευσης στη γλώσσα προγραμματισμού.
- Κόστος δημιουργίας προγραμμάτων. Οι αρχικές προσπάθειες κατασκευής γλωσσών προγραμματισμού βασίζονταν στην επιθυμία μείωσης του κόστους ανάπτυξης του λογισμικού.
- Κόστος μεταγλώττισης.
- Κόστος εκτέλεσης. Μολονότι στο παρελθόν η αποδοτικότητα της εκτέλεσης ήταν ο κύριος παράγοντας που λαμβάνονταν υπόψη κατά τη σχεδίαση των πρώτων γλωσσών προγραμματισμού, σήμερα δεν έχει μεγάλη σημασία στις περισσότερες περιπτώσεις.
- Κόστος συστήματος υλοποίησης της γλώσσας προγραμματισμού (π.χ. Java->δωρεάν, Ada->ακριβοί μεταγλωττιστές πρώτης γενιάς)
- Κόστος αναξιοπιστίας (πιθανότητα αγωγών αποζημίωσης).
- Κόστος συντήρησης προγραμμάτων. Εκτιμάται ότι το κόστος συντήρησης μπορεί να είναι διπλάσιο ή ακόμα και τετραπλάσιο από το κόστος ανάπτυξης του λογισμικού.
Χαρακτηριστικά
Απλότητα
Πολλαπλότητα χαρακτηριστικών (feature multiplicity): Οι πολλαπλοί τρόποι με τους οποίους μπορεί να επιτευχθεί ένα αποτέλεσμα επηρεάζουν την απλότητα. Για παράδειγμα στον ακόλουθο κώδικα σε Java η μοναδιαία αύξηση της μεταβλητής count επιτυγχάνεται με 4 διαφορετικούς τρόπους.
Υπερφόρτωση τελεστών: Ερμηνεία ενός τελεστή με περισσότερους από έναν τρόπους. Για παράδειγμα η υπερφόρτωση του τελεστή + για διανύσματα μπορεί να υλοποιηθεί με διαφορετικούς και μη διαισθητικά ορθούς τρόπους.
Η υπερβολική απλότητα μιας γλώσσας μπορεί να υποβαθμίζει την αναγνωσιμότητα των προγραμμάτων που είναι γραμμένα σε αυτή (π.χ. Assembly).
Ορθογωνικότητα
Ορθογωνικότητα σημαίνει ότι λίγα μόνο πρωτογενή δομικά στοιχεία μπορούν να συνδυαστούν με σχετικά λίγους τρόπους έτσι ώστε να κατασκευαστούν οι δομές ελέγχου και δεδομένων της γλώσσας. Επιπλέον, κάθε πιθανός συνδυασμός των πρωτογενών δομικών στοιχείων είναι έγκυρος και έχει νόημα.
Για παράδειγμα σε μια υποθετική γλώσσα με τύπους δεδομένων int, float, char και τους τελεστές τύπων διατάξεων και δεικτών, η έλλειψη ορθογωνικότητας θα σήμαινε ότι δεν θα μπορούσε να οριστεί κάποιος συνδυασμός τύπου δεδομένων και τελεστή τύπων όπως ο δείκτης σε διάταξη.
Μεγαλύτερη ορθογωνικότητα συνεπάγεται λιγότερες εξαιρέσεις σε κανόνες της γλώσσας.
Οι συναρτησιακές γλώσσες (Lisp, Scheme, Haskell, ...) προσφέρουν μεγαλύτερη ορθογωνικότητα από προστακτικές γλώσσες (C, C++, Java, Python, ...), αλλά υστερούν σε ταχύτητα εκτέλεσης.
Παραδείγματα έλλειψης ορθογωνικότητας στη C
Παράδειγμα 1: Μολονότι η C διαθέτει 3 είδη δομημένων τύπων δεδομένων, τις διατάξεις (πίνακες), τις εγγραφές (struct) και τις ενώσεις (union), οι εγγραφές και οι ενώσεις μπορούν να επιστρέφονται από συναρτήσεις αλλά όχι οι διατάξεις. Ωστόσο, θα πρέπει να σημειωθεί ότι η C επιτρέπει την επιστροφή ενός δείκτη προς μια διάταξη και αυτό αποτελεί έναν έμμεσο τρόπο επιστροφής διάταξης από συνάρτηση.
Μεταγλώττιση και εκτέλεση του κώδικα:
$ gcc orthogonality_violation1.c
$ ./a.out
1. Function returns struct
1 2
2. Function returns array (through a pointer)
a[0]=42
a[1]=42
a[2]=42
a[3]=42
a[4]=42
Παράδειγμα 2: Ένα μέλος μιας δομής μπορεί να είναι οποιουδήποτε τύπου δεδομένων εκτός από void ή μια δομή του ίδιου τύπου. Ωστόσο, ένα μέλος μιας δομής μπορεί να είναι δείκτης προς void ή δείκτης προς δομή του ίδιου τύπου.
Μεταγλώττιση και εκτέλεση του κώδικα:
$ gcc orthogonality_violation2.c
$ ./a.out
x.a=1
x.b=3.140000
x.c=a
x.d=2.718000
x.e=1,2,3
x.f points to a struct of size 56 bytes
x.g=42
Παράδειγμα 3: Ένα στοιχείο μιας διάταξης μπορεί να είναι οποιοδήποτε τύπος δεδομένων εκτός από void ή μια συνάρτηση. Ωστόσο, μπορεί να είναι δείκτης προς void ή δείκτης προς συνάρτηση.
Μεταγλώττιση και εκτέλεση του κώδικα:
Παράδειγμα 4: Οι παράμετροι συναρτήσεων περνούν με τιμή εκτός αν είναι διατάξεις οπότε περνούν με αναφορά (επειδή η εμφάνιση του ονόματος μιας διάταξης χωρίς δείκτη ερμηνεύεται ως η διεύθυνση του πρώτου στοιχείου της διάταξης)
orthogonality_violation4.c | |
---|---|
Μεταγλώττιση και εκτέλεση του κώδικα:
Παράδειγμα 5: Εξάρτηση από τα συμφραζόμενα για τον τελεστή + στην έκφραση a + b. Αν το a είναι δείκτης προς μια τιμή float που καταλαμβάνει 4 bytes τότε η τιμή του b θα πολλαπλασιαστεί επί 4 πριν προστεθεί στο a.
orthogonality_violation5.c | |
---|---|
Μεταγλώττιση και εκτέλεση του κώδικα:
Τύποι δεδομένων
- Μια γλώσσα προγραμματισμού που περιέχει τύπο δεδομένων Boolean είναι περισσότερη εκφραστική έναντι κάποιας γλώσσας που δεν περιέχει τύπο δεδομένων Boolean και χρησιμοποιεί ειδικές τιμές για να υποδηλώσει το αληθές ή ψευδές.
Μεταγλώττιση και εκτέλεση του κώδικα:
$ gcc boolean1.c
$ ./a.out
1 is considered to be true
-1 is considered to be true
3.14 is considered to be true
-3.14 is considered to be true
a is considered to be true
0 is considered to be false
0.0 is considered to be false
0.0f is considered to be false
NULL is considered to be false
boolean2.c | |
---|---|
Μεταγλώττιση και εκτέλεση του κώδικα:
$ gcc boolean2.c
$ ./a.out
true value is supported in C99 or later
false value is supported in C99 or later
Σχεδίαση συντακτικού
- H Ada χρησιμοποιεί το end if ώστε να τερματίσει μια δομή επιλογής και το end loop για να τερματίσει μια δομή επανάληψης. Αντίθετα η C χρησιμοποιεί την αγκύλη } για το τερματισμό όλων των μπλοκ εντολών. Ποιο είναι καλύτερο;
- Η Fortran 95 επιτρέπει τον ορισμό μεταβλητών με το ίδιο όνομα με λέξεις με ειδική σημασία στη γλώσσα όπως οι Do και End. Η C δεν επιτρέπει τη χρήση λέξεων όπως η for, if κλπ και τις θεωρεί δεσμευμένες. Ποιο είναι καλύτερο;
Εκφραστικότητα
- Η Ada διαθέτει τους λογικούς τελεστές and then και οr else που αποτελούν πρακτικούς τρόπους σύντομης αποτίμησης μιας boolean έκφρασης
Έλεγχος τύπων
- Ο έλεγχος τύπων κατά την εκτέλεση είναι "ακριβός" και προτιμάται ο έλεγχος τύπων κατά τη μεταγλώττιση.
- linters της C
Χειρισμός εξαιρέσεων
Με τη χρήση εξαιρέσεων (exceptions) δίνεται η δυνατότητα του χειρισμού σφαλμάτων εκτέλεσης έτσι ώστε να πραγματοποιούνται διορθωτικές κινήσεις. Με τον τρόπο αυτό αυξάνεται η αξιοπιστία του προγράμματος.
- Οι C++, C#, Java, Python κ.α. έχουν δυνατότητες χειρισμού εξαιρέσεων.
- Η C δεν έχει μηχανισμό χειρισμό εξαιρέσεων.
Ψευδωνυμία
Η ψευδωνυμία αναφέρεται στη χρήση δύο ή περισσότερων ονομάτων για πρόσβαση δεδομένων που βρίσκονται στην ίδια θέση μνήμης.
Ψευδώνυμα (aliases) στη C
alias.c | |
---|---|
Μεταγλώττιση και εκτέλεση του κώδικα:
Ψευδώνυμα (aliases) στη C++
alias.cpp | |
---|---|
Μεταγλώττιση και εκτέλεση του κώδικα:
Ψευδώνυμα (aliases) στην Python
1.4 Επιρροές στη σχεδίαση γλωσσών προγραμματισμού
Οι πλέον σημαντικοί παράγοντες που επηρεάζουν τη σχεδίαση των γλωσσών προγραμματισμού είναι η αρχιτεκτονική των Η/Υ και οι μεθοδολογίες σχεδίασης προγραμμάτων.
1.4.1 Αρχιτεκτονική υπολογιστών
- Η αρχιτεκτονική von Neumann (δεδομένα και προγράμματα στην ίδια μνήμη, ξεχωριστή Κεντρική Μονάδα Επεξεργασίας από τη μνήμη) επηρέασε τη σχεδίαση των γλωσσών προγραμματισμού και οδήγησε στις προστακτικές γλώσσες (Fortran, C, C++, Java, Python).
- μεταβλητές → κελιά μνήμης
- εκχώρηση → προσκόμιση δεδομένων από τη μνήμη στον επεξεργαστή, επεξεργασία, αποθήκευση αποτελεσμάτων στη μνήμη
- επανάληψη → μέσω εντολών διακλάδωσης
- Η εκτέλεση ενός προγράμματος σε κώδικα μηχανής σε έναν Η/Υ αρχιτεκτονικής von Neumann συμβαίνει εκτελώντας κύκλους προσκόμισης και εκτέλεσης εντολών.
- Η επανάληψη εκτέλεσης εντολών καθίσταται εύκολη και απλούστερος τρόπος προσέγγισης από την αναδρομή.
- Οι συναρτησιακές γλώσσες (π.χ. Lisp, Scheme, Haskell) στηρίζονται στην εφαρμογή συναρτήσεων σε δεδομένες παραστάσεις, δεν έχουν μεταβλητές, προτάσεις εκχώρησης και εντολές επανάληψης. Συνεπώς, απομακρύνονται εννοιολογικά από την αρχιτεκτονική von Neumann και το γεγονός αυτό τις καθιστά λιγότερο αποδοτικές εφόσον εκτελούνται σε υπολογιστές αρχιτεκτονικής von Neumann.
1.4.2 Μεθοδολογίες σχεδίασης προγραμματισμού
- Δομημένος προγραμματισμός
- Σχεδιασμός από πάνω προς τα κάτω
- Προοδευτική εκλέπτυνση
- Αφηρημένοι Τύποι Δεδομένων
- Αντικειμενοστραφής προγραμματισμός (αφαίρεση δεδομένων, ενθυλάκωση επεξεργασίας, κληρονομικότητα, δυναμική σύνδεση μεθόδων)
1.5 Κατηγορίες γλωσσών προγραμματισμού
Οι βασικές κατηγορίες γλωσσών προγραμματισμού είναι:
- προστακτικές (C, C++, C#, Java, Python, ...)
- συναρτησιακές (Lisp, Scheme, F#, Haskell, ...)
- λογικές (Prolog, ...)
- αντικειμενοστραφείς (C++, Java, C#, ...)
- σεναρίων (Python, Perl, JavaScript, Ruby, ...)
- υβριδικές γλώσσες σημείωσης/προγραμματισμού (JSPTL, XSLT, ...)
1.6 Συμβιβασμοί στη σχεδίαση γλωσσών προγραμματισμού
Κατά το σχεδιασμό γλωσσών υπάρχουν πάρα πολλά αντικρουόμενα κριτήρια. Για παράδειγμα:
- αξιοπιστία έναντι ταχύτητα εκτέλεσης (π.χ. η Java ελέγχει τη πρόσβαση εντός ορίων σε διατάξεις ενώ η C και η C++ για λόγους ταχύτητας δεν πραγματοποιούν τέτοιους ελέγχους)
- αξιοπιστία έναντι ευχέρειας χειρισμών (π.χ. η Java δεν υποστηρίζει δείκτες ενώ οι C και η C++ υποστηρίζουν)
- συμπυκνωμένη σύνταξη έναντι ευκολίας ανάγνωσης (π.χ. η APL υποστηρίζει πολλούς τελεστές για πράξεις σε πίνακες αλλά τα προγράμματα παρουσιάζουν δυσκολίες ανάγνωσης)
1.7 Μέθοδοι υλοποίησης
1.7.1 Μεταγλώττιση
- Τα προγράμματα μεταφράζονται σε γλώσσα μηχανής και στη συνέχεια εκτελούνται ταχύτατα.
- πηγαίος κώδικας → λεκτικός αναλυτής → λεκτικές μονάδες
- λεκτικές μονάδες → συντακτικός αναλυτής → δένδρο συντακτικής ανάλυσης
- δένδρο συντακτικής ανάλυσης → γεννήτρια ενδιάμεσου κώδικα (σημασιολογικός αναλυτής + βελτιστοποιητής) → ενδιάμεσος κώδικας
- ενδιάμεσος κώδικας → γεννήτρια κώδικα → γλώσσα μηχανής
- Η διαδικασία μεταγλώττισης χρησιμοποιεί τον πίνακα συμβόλων που συμπληρώνεται από το λεκτικό και το συντακτικό αναλυτή ενώ χρησιμοποιείται από το σημασιολογικό αναλυτή και τη γεννήτρια κώδικα.
- Η γλώσσα μηχανής που παράγει ο μεταγλωττιστής συνήθως δεν είναι σε θέση να εκτελεστεί απευθείας αλλά πρέπει να συνδεθεί με άλλα τμήματα κώδικα του συστήματος (π.χ. για είσοδο/έξοδο) ή με προγράμματα του χρήστη που έχουν ήδη μεταγλωττιστεί και έχουν τοποθετηθεί σε βιβλιοθήκες σε μια διαδικασία που λέγεται σύνδεση.
- Το αδιέξοδο von Neumann αναφέρεται στο όριο ταχύτητας που θέτει η σύνδεση ανάμεσα στη μνήμη και στον επεξεργαστή.
- Γλώσσες με μεταγλωττιστή: C, C++, Fortran, ...
1.7.2 Καθαρή διερμηνεία
- Στην καθαρή διερμηνεία η εκτέλεση του προγράμματος πραγματοποιείται από ένα πρόγραμμα που ονομάζεται διερμηνέας και το οποίο παρέχει μια εικονική μηχανή για τη γλώσσα.
- Ο διερμηνέας εκτελεί εντολή προς εντολή το πρόγραμμα, δεχόμενος όπου χρειάζεται είσοδο.
- Η εκσφαλμάτωση του προγράμματος καθίσταται ευκολότερη.
- Η ταχύτητα εκτέλεσης είναι χαμηλότερη από τα μεταγλωττιζόμενα προγράμματα καθώς χρειάζεται να γίνεται κατά την εκτέλεση αποκωδικοποίηση κάθε εντολής του προγράμματος. Η διαδικασία αυτή είναι πολύ συνθετότερη από την εκτέλεση οδηγιών κώδικα μηχανής.
- Κάθε φορά που εκτελείται μια πρόταση πρέπει να αποκωδικοποιείται ξανά.
- Απαιτεί περισσότερο χώρο καθώς εκτός από το πηγαίο πρόγραμμα πρέπει να υπάρχει και ο πίνακας συμβόλων.
- Γλώσσες με καθαρό διερμηνευτή: JavaScript, PHP, ...
1.7.3 Υβριδικά συστήματα υλοποίησης
- Αποτελούν συμβιβαστική λύση ανάμεσα σε μεταγλωττιστές και καθαρούς διερμηνευτές.
- Τα προγράμματα μεταφράζονται σε μια ενδιάμεση γλώσσα που επιτρέπει την εύκολη διερμηνεία.
- Γλώσσες με υβριδικό σύστημα υλοποίησης: Perl, ...
Συστήματα JIT (Just In Time)
Ένα σύστημα JIT αρχικά μεταφράζει το πρόγραμμα σε ενδιάμεση γλώσσα. Στη συνέχεια, κατά την εκτέλεση μεταγλωττίζει τις μεθόδους της ενδιάμεσης γλώσσας σε κώδικα μηχανής όταν εκτελούνται, έτσι ώστε μελλοντικές εκτελέσεις τους να είναι ταχύτερες. Συστήματα JIT χρησιμοποιούνται στη Java και στις γλώσσες .NET (C#, F#, VB.NET, JScript, C++/CLI)
1.7.4 Προεπεξεργαστές
Ο προεπεξεργαστής είναι ένα πρόγραμμα που επεξεργάζεται ένα πρόγραμμα πριν το πρόγραμμα μεταγλωττιστεί.
Παραδείγματα με τον προεπεξεργαστή της C
Εντολές προεπεξεργαστή: #include, #define, #ifdef, #ifndef, #else, #elif, #endif, #pragma
preprocessor1.c | |
---|---|
Αν δοθεί η ακόλουθη εντολή μεταγλώττισης (με τον διακόπτη -E):
τότε η έξοδος θα είναι:
1.8 Προγραμματιστικά περιβάλλοντα
Ένα προγραμματιστικό περιβάλλον είναι μια συλλογή εργαλείων που χρησιμοποιούνται για την ανάπτυξη λογισμικού.
Editors
IDEs
- Microsoft Visual Studio (C#, C, C++, ...)
- Jetbrains
- IntelliJ (Java, Kotlin, Scala, Groovy)
- PyCharm (Python)
- Clion (C, C++)
- Eclipse (Java, C, C++, ...)
- NetBeans (Java, C, C++, ...)
- Codelite (C, C++, PHP, JavaScript)
- Code::Blocks (C, C++, Fortran)
- Xcode (Objective-C, C, C++, Swift)