Η κατανόηση του τρόπου οργάνωσης των υπολογιστών, του τρόπου με τον οποίο φαίνεται να λειτουργούν σε πολύ χαμηλό επίπεδο, είναι απαραίτητη για την κατανόηση του τρόπου λειτουργίας ενός προγράμματος σε γλώσσα assembly. Στο πιο απλοϊκό επίπεδο, οι υπολογιστές έχουν τρία βασικά μέρη:
- κύρια μνήμη ή RAM που περιέχει δεδομένα και εντολές,
- έναν επεξεργαστή, ο οποίος επεξεργάζεται τα δεδομένα εκτελώντας τις εντολές, και
- είσοδος και έξοδος (μερικές φορές συντομεύεται ως I/O), που επιτρέπουν στον υπολογιστή να επικοινωνεί με τον έξω κόσμο και να αποθηκεύει δεδομένα εκτός της κύριας μνήμης, ώστε να μπορεί να τα πάρει πίσω αργότερα.
Κύρια μνήμη
Στους περισσότερους υπολογιστές, η μνήμη χωρίζεται σε bytes. Κάθε byte περιέχει 8 bits. Κάθε byte στη μνήμη έχει επίσης μια διεύθυνση, η οποία είναι ένας αριθμός που λέει πού βρίσκεται το byte στη μνήμη. Το πρώτο byte στη μνήμη έχει διεύθυνση 0, το επόμενο έχει διεύθυνση 1 κ.ο.κ. Ο διαχωρισμός της μνήμης σε bytes την καθιστά διευθυνσιοδοτούμενη, επειδή κάθε byte αποκτά μια μοναδική διεύθυνση. Οι διευθύνσεις της μνήμης byte δεν μπορούν να χρησιμοποιηθούν για να αναφερθούν σε ένα μεμονωμένο bit ενός byte. Ένα byte είναι το μικρότερο κομμάτι μνήμης που μπορεί να διευθυνσιοδοτηθεί.
Παρόλο που μια διεύθυνση αναφέρεται σε ένα συγκεκριμένο byte στη μνήμη, οι επεξεργαστές επιτρέπουν τη χρήση πολλών bytes μνήμης στη σειρά. Η πιο συνηθισμένη χρήση αυτής της δυνατότητας είναι η χρήση 2 ή 4 bytes στη σειρά για την αναπαράσταση ενός αριθμού, συνήθως ενός ακέραιου. Μερικές φορές χρησιμοποιούνται και μεμονωμένα bytes για την αναπαράσταση ακεραίων αριθμών, αλλά επειδή έχουν μήκος μόνο 8 bit, μπορούν να χωρέσουν μόνο 2 8ή 256 διαφορετικές πιθανές τιμές. Η χρήση 2 ή 4 bytes σε μια σειρά αυξάνει τον αριθμό των διαφορετικών πιθανών τιμών σε 2 16, 65536 ή 2 32, 4294967296, αντίστοιχα.
Όταν ένα πρόγραμμα χρησιμοποιεί ένα byte ή έναν αριθμό bytes σε μια σειρά για να αναπαραστήσει κάτι όπως ένα γράμμα, έναν αριθμό ή οτιδήποτε άλλο, αυτά τα bytes ονομάζονται αντικείμενο επειδή είναι όλα μέρος του ίδιου πράγματος. Παρόλο που όλα τα αντικείμενα αποθηκεύονται σε πανομοιότυπα bytes της μνήμης, αντιμετωπίζονται σαν να έχουν έναν "τύπο", ο οποίος λέει πώς πρέπει να κατανοηθούν τα bytes: είτε ως ακέραιος αριθμός είτε ως χαρακτήρας είτε ως κάποιος άλλος τύπος (όπως μια μη ακέραια τιμή). Ο κώδικας μηχανής μπορεί επίσης να θεωρηθεί ως ένας τύπος που ερμηνεύεται ως εντολές. Η έννοια του τύπου είναι πολύ, πολύ σημαντική, επειδή ορίζει τι πράγματα μπορούν και τι δεν μπορούν να γίνουν στο αντικείμενο και πώς να ερμηνεύονται τα bytes του αντικειμένου. Για παράδειγμα, δεν είναι έγκυρο να αποθηκεύσετε έναν αρνητικό αριθμό σε ένα αντικείμενο θετικού αριθμού και δεν είναι έγκυρο να αποθηκεύσετε ένα κλάσμα σε έναν ακέραιο.
Μια διεύθυνση που δείχνει (είναι η διεύθυνση ενός αντικειμένου πολλών byte) είναι η διεύθυνση του πρώτου byte του αντικειμένου αυτού - το byte που έχει τη χαμηλότερη διεύθυνση. Παρεμπιπτόντως, ένα σημαντικό πράγμα που πρέπει να σημειωθεί είναι ότι δεν μπορείτε να πείτε ποιος είναι ο τύπος ενός αντικειμένου - ή ακόμη και το μέγεθός του - από τη διεύθυνσή του. Για την ακρίβεια, δεν μπορείτε καν να καταλάβετε τον τύπο ενός αντικειμένου κοιτάζοντάς το. Ένα πρόγραμμα σε γλώσσα assembly πρέπει να παρακολουθεί ποιες διευθύνσεις μνήμης περιέχουν ποια αντικείμενα και πόσο μεγάλα είναι αυτά τα αντικείμενα. Ένα πρόγραμμα που το κάνει αυτό είναι ασφαλές ως προς τον τύπο, επειδή κάνει μόνο πράγματα στα αντικείμενα που είναι ασφαλές να γίνουν με βάση τον τύπο τους. Ένα πρόγραμμα που δεν το κάνει, πιθανόν να μην λειτουργεί σωστά. Σημειώστε ότι τα περισσότερα προγράμματα στην πραγματικότητα δεν αποθηκεύουν ρητά ποιος είναι ο τύπος ενός αντικειμένου, απλώς προσπελαύνουν τα αντικείμενα με συνέπεια - το ίδιο αντικείμενο αντιμετωπίζεται πάντα ως ο ίδιος τύπος.
Ο επεξεργαστής
Ο επεξεργαστής εκτελεί (εκτελεί) εντολές, οι οποίες είναι αποθηκευμένες ως κώδικας μηχανής στην κύρια μνήμη. Εκτός από τη δυνατότητα πρόσβασης στη μνήμη για αποθήκευση, οι περισσότεροι επεξεργαστές διαθέτουν μερικούς μικρούς, γρήγορους και σταθερού μεγέθους χώρους για την αποθήκευση των αντικειμένων με τα οποία εργάζονται επί του παρόντος. Αυτοί οι χώροι ονομάζονται καταχωρητές. Οι επεξεργαστές εκτελούν συνήθως τρεις τύπους εντολών, αν και ορισμένες εντολές μπορεί να είναι συνδυασμός αυτών των τύπων. Παρακάτω παρατίθενται μερικά παραδείγματα κάθε τύπου σε γλώσσα συναρμολόγησης x86.
Εντολές που διαβάζουν ή γράφουν μνήμη
Η ακόλουθη εντολή της γλώσσας συναρμολόγησης x86 διαβάζει (φορτώνει) ένα αντικείμενο 2 byte από το byte στη διεύθυνση 4096 (0x1000 σε δεκαεξαδικό σύστημα) σε έναν καταχωρητή 16 bit που ονομάζεται 'ax':
mov ax, [1000h]
Σε αυτή τη γλώσσα συναρμολόγησης, οι αγκύλες γύρω από έναν αριθμό (ή ένα όνομα καταχωρητή) σημαίνουν ότι ο αριθμός πρέπει να χρησιμοποιηθεί ως διεύθυνση για τα δεδομένα που πρέπει να χρησιμοποιηθούν. Η χρήση μιας διεύθυνσης για να δείξει σε δεδομένα ονομάζεται ανακατεύθυνση. Σε αυτό το επόμενο παράδειγμα, χωρίς τις αγκύλες, ένας άλλος καταχωρητής, ο bx, παίρνει στην πραγματικότητα την τιμή 20 που φορτώνεται σε αυτόν.
mov bx, 20
Επειδή δεν χρησιμοποιήθηκε καμία παρέκβαση, η ίδια η πραγματική τιμή τοποθετήθηκε στον καταχωρητή.
Εάν οι τελεστές (τα πράγματα που ακολουθούν το μνημονικό) εμφανίζονται με αντίστροφη σειρά, μια εντολή που φορτώνει κάτι από τη μνήμη αντί να το γράφει στη μνήμη:
mov [1000h], ax
Εδώ, η μνήμη στη διεύθυνση 1000h λαμβάνει την τιμή ax. Αν αυτό το παράδειγμα εκτελεστεί αμέσως μετά το προηγούμενο, τα 2 bytes στις 1000h και 1001h θα είναι ένας ακέραιος αριθμός 2 bytes με την τιμή 20.
Εντολές που εκτελούν μαθηματικές ή λογικές πράξεις
Ορισμένες εντολές κάνουν πράγματα όπως η αφαίρεση ή λογικές πράξεις όπως το not:
Το παράδειγμα του κώδικα μηχανής νωρίτερα σε αυτό το άρθρο θα ήταν αυτό σε γλώσσα assembly:
προσθέστε ax, 42
Εδώ, τα 42 και ax αθροίζονται και το αποτέλεσμα αποθηκεύεται στο ax. Στη συναρμολόγηση x86 είναι επίσης δυνατό να συνδυάσετε μια προσπέλαση μνήμης και μια μαθηματική πράξη όπως αυτή:
add ax, [1000h]
Αυτή η εντολή προσθέτει την τιμή του ακέραιου αριθμού 2 byte που είναι αποθηκευμένος στο 1000h στο ax και αποθηκεύει την απάντηση στο ax.
ή ax, bx
Αυτή η εντολή υπολογίζει το ή των περιεχομένων των καταχωρητών ax και bx και αποθηκεύει το αποτέλεσμα πίσω στον ax.
Εντολές που αποφασίζουν ποια θα είναι η επόμενη εντολή
Συνήθως, οι εντολές εκτελούνται με τη σειρά που εμφανίζονται στη μνήμη, η οποία είναι η σειρά με την οποία πληκτρολογούνται στον κώδικα συναρμολόγησης. Ο επεξεργαστής απλώς τις εκτελεί τη μία μετά την άλλη. Ωστόσο, προκειμένου οι επεξεργαστές να κάνουν περίπλοκα πράγματα, πρέπει να εκτελούν διαφορετικές εντολές ανάλογα με το ποια είναι τα δεδομένα που τους δόθηκαν. Η ικανότητα των επεξεργαστών να εκτελούν διαφορετικές εντολές ανάλογα με το αποτέλεσμα κάποιου πράγματος ονομάζεται διακλάδωση. Οι εντολές που αποφασίζουν ποια θα είναι η επόμενη εντολή ονομάζονται εντολές διακλάδωσης.
Σε αυτό το παράδειγμα, ας υποθέσουμε ότι κάποιος θέλει να υπολογίσει την ποσότητα μπογιάς που θα χρειαστεί για να βάψει ένα τετράγωνο με συγκεκριμένο μήκος πλευράς. Ωστόσο, λόγω της οικονομίας κλίμακας, το χρωματοπωλείο δεν θα του πουλήσει λιγότερη ποσότητα χρώματος από την ποσότητα χρώματος που χρειάζεται για να βάψει ένα τετράγωνο 100 x 100.
Για να υπολογίσουν την ποσότητα χρώματος που θα χρειαστούν με βάση το μήκος του τετραγώνου που θέλουν να βάψουν, καταλήγουν σε αυτό το σύνολο βημάτων:
- αφαιρέστε 100 από το μήκος της πλευράς
- αν η απάντηση είναι μικρότερη του μηδενός, θέστε το μήκος της πλευράς σε 100
- πολλαπλασιάστε το μήκος της πλευράς με τον εαυτό του
Αυτός ο αλγόριθμος μπορεί να εκφραστεί με τον ακόλουθο κώδικα όπου ax είναι το μήκος της πλευράς.
mov bx, ax sub bx, 100 jge continue mov ax, 100 continue: mul ax
Αυτό το παράδειγμα εισάγει αρκετά νέα πράγματα, αλλά οι δύο πρώτες εντολές είναι γνωστές. Αντιγράφουν την τιμή του ax στο bx και στη συνέχεια αφαιρούν 100 από το bx.
Ένα από τα νέα πράγματα σε αυτό το παράδειγμα ονομάζεται ετικέτα (label), μια έννοια που συναντάται στις γλώσσες συναρμολόγησης γενικά. Οι ετικέτες μπορούν να είναι οτιδήποτε θέλει ο προγραμματιστής (εκτός αν είναι το όνομα μιας εντολής, το οποίο θα προκαλούσε σύγχυση στον assembler). Σε αυτό το παράδειγμα, η ετικέτα είναι 'continue'. Ερμηνεύεται από τον assembler ως η διεύθυνση μιας εντολής. Σε αυτή την περίπτωση, είναι η διεύθυνση της mult ax.
Μια άλλη νέα έννοια είναι αυτή των σημαιών. Στους επεξεργαστές x86, πολλές εντολές θέτουν "σημαίες" στον επεξεργαστή, οι οποίες μπορούν να χρησιμοποιηθούν από την επόμενη εντολή για να αποφασιστεί τι θα γίνει. Σε αυτή την περίπτωση, αν το bx ήταν μικρότερο από 100, η sub θα θέσει μια σημαία που λέει ότι το αποτέλεσμα ήταν μικρότερο από μηδέν.
Η επόμενη εντολή είναι η jge, η οποία είναι συντομογραφία για το 'Jump if Greater than or Equal to'. Πρόκειται για εντολή διακλάδωσης. Εάν οι σημαίες στον επεξεργαστή καθορίζουν ότι το αποτέλεσμα ήταν μεγαλύτερο ή ίσο του μηδενός, αντί να μεταβεί απλώς στην επόμενη εντολή, ο επεξεργαστής θα μεταβεί στην εντολή στην ετικέτα continue, η οποία είναι mul ax.
Αυτό το παράδειγμα λειτουργεί μια χαρά, αλλά δεν είναι αυτό που θα έγραφαν οι περισσότεροι προγραμματιστές. Η εντολή αφαίρεσης έθεσε σωστά τη σημαία, αλλά αλλάζει επίσης την τιμή στην οποία λειτουργεί, πράγμα που απαιτούσε την αντιγραφή του ax στο bx. Οι περισσότερες γλώσσες συναρμολόγησης επιτρέπουν εντολές σύγκρισης που δεν αλλάζουν κανένα από τα ορίσματα που περνούν, αλλά εξακολουθούν να θέτουν σωστά τις σημαίες και η συναρμολόγηση x86 δεν αποτελεί εξαίρεση.
cmp ax, 100 jge continue mov ax, 100 continue: mul ax
Τώρα, αντί να αφαιρέσουμε το 100 από το ax, να δούμε αν ο αριθμός αυτός είναι μικρότερος από το μηδέν και να τον αναθέσουμε πίσω στο ax, το ax παραμένει αμετάβλητο. Οι σημαίες εξακολουθούν να τίθενται με τον ίδιο τρόπο και το άλμα εξακολουθεί να πραγματοποιείται στις ίδιες καταστάσεις.
Είσοδος και έξοδος
Ενώ η είσοδος και η έξοδος αποτελούν θεμελιώδες μέρος της πληροφορικής, δεν υπάρχει ένας μόνο τρόπος για να γίνουν στη γλώσσα assembly. Αυτό οφείλεται στο γεγονός ότι ο τρόπος λειτουργίας των εισόδων/εξόδων εξαρτάται από τη ρύθμιση του υπολογιστή και το λειτουργικό σύστημα που τρέχει, και όχι μόνο από το είδος του επεξεργαστή που διαθέτει. Στην ενότητα των παραδειγμάτων το παράδειγμα Hello World χρησιμοποιεί κλήσεις του λειτουργικού συστήματος MS-DOS και το παράδειγμα μετά από αυτό χρησιμοποιεί κλήσεις του BIOS.
Είναι δυνατή η εκτέλεση εισόδου/εξόδου σε γλώσσα συναρμολόγησης. Πράγματι, η γλώσσα assembly μπορεί γενικά να εκφράσει οτιδήποτε μπορεί να κάνει ένας υπολογιστής. Ωστόσο, παρόλο που υπάρχουν εντολές πρόσθεσης και διακλάδωσης στη γλώσσα assembly που θα κάνουν πάντα το ίδιο πράγμα, δεν υπάρχουν εντολές στη γλώσσα assembly που να κάνουν πάντα I/O.
Το σημαντικό πράγμα που πρέπει να σημειωθεί είναι ότι ο τρόπος με τον οποίο λειτουργεί το I/O δεν αποτελεί μέρος οποιασδήποτε γλώσσας συναρμολόγησης, επειδή δεν αποτελεί μέρος του τρόπου με τον οποίο λειτουργεί ο επεξεργαστής.