Μετάβαση στο περιεχόμενο
Topos Uranos

Περίληψη

Ένα πρόγραμμα που δίνει λάθος αποτέλεσμα δεν διορθώνεται αλλάζοντας γραμμές στην τύχη, αλλά με έρευνα. Αυτό το μάθημα παρουσιάζει την αποσφαλμάτωση ως πειραματική μέθοδο: αναπαράγουμε την αστοχία, διατυπώνουμε μια υπόθεση για την αιτία της, σχεδιάζουμε ένα πείραμα που την επιβεβαιώνει ή την απορρίπτει, και περιορίζουμε τη θέση του σφάλματος· η διχοτόμηση το βρίσκει σε αριθμό βημάτων που αυξάνεται όπως ο λογάριθμος του μεγέθους του προγράμματος. Αναπτύσσει το εργαλείο (τη μεταγλώττιση με -g και χωρίς βελτιστοποίηση, τη ρύθμιση του Visual Studio Code στα τρία συστήματα και μια πραγματική συνεδρία του GDB που δείχνει τι κάνει ο επεξεργαστής από κάτω), τα απλά σημεία διακοπής και τα σημεία διακοπής υπό συνθήκη, τα βήματα, τη στοίβα κλήσεων και την επιθεώρηση της κατάστασης, με ένα κεντρικό παράδειγμα: τη διάμεσο που αστοχεί μόνο με άρτιο πλήθος δεδομένων. Κλείνει με τις στρατηγικές που αξίζουν περισσότερο από το εργαλείο (ελαχιστοποίηση της περίπτωσης, αναζήτηση του τελευταίου σωστού σημείου, έλεγχος αναλλοίωτων με assert, ανάγνωση της αναφοράς του ανιχνευτή) και με τρεις περιπτώσεις: ένα σφάλμα κατά ένα, μια αναδρομή που δεν τερματίζει και μια ακυρωμένη αναφορά.

Μαθησιακοί στόχοι

  1. Να ρυθμίζει τη μεταγλώττιση με πληροφορίες αποσφαλμάτωσης (-g, χωρίς βελτιστοποίηση) και να εκκινεί τον αποσφαλματωτή από το Visual Studio Code στο δικό του σύστημα.
  2. Να χρησιμοποιεί απλά σημεία διακοπής και σημεία διακοπής υπό συνθήκη, την εκτέλεση βήμα προς βήμα (είσοδος, υπερπήδηση, έξοδος) και την επιθεώρηση μεταβλητών, εκφράσεων και της στοίβας κλήσεων.
  3. Να εφαρμόζει μια συστηματική στρατηγική (αναπαραγωγή, ελαχιστοποίηση, διχοτόμηση, διατύπωση και έλεγχος υποθέσεων) για να εντοπίσει ένα δοσμένο σφάλμα, τεκμηριώνοντας κάθε υπόθεση.
  4. Να συνδυάζει τον αποσφαλματωτή με τους ανιχνευτές και με τις δοκιμές για να εντοπίσει ένα σφάλμα μνήμης από την αναφορά του.

Αποσφαλμάτωση σημαίνει έρευνα

Το σφάλμα και η αστοχία

Η αστοχία είναι αυτό που παρατηρείται: ένα λάθος αποτέλεσμα, ένα πρόγραμμα που δεν τερματίζει, μια αναφορά του ανιχνευτή. Το σφάλμα (ή ελάττωμα) είναι η αιτία: μια γραμμή, μια συνθήκη ή μια σχεδιαστική απόφαση που δεν αντιστοιχεί στην προδιαγραφή. Η αστοχία φαίνεται· το σφάλμα πρέπει να το βρούμε, και μπορεί να βρίσκεται μακριά της. Από αυτό έπεται ότι αν διορθώσουμε εκεί όπου φαίνεται η αστοχία, για παράδειγμα αφαιρώντας 2 από το αποτέλεσμα επειδή «βγαίνει πάντα 2 παραπάνω», διορθώνουμε το σύμπτωμα και αφήνουμε την αιτία, που θα επιστρέψει με άλλα δεδομένα.

Αν αυτό ισχύει, η αποσφαλμάτωση είναι έρευνα για την αιτία, και η μέθοδός της είναι η μέθοδος των πειραματικών επιστημών:

  1. Παρατήρηση και αναπαραγωγή. Περιγράφουμε με ακρίβεια την αστοχία (με ποια είσοδο, τι αναμενόταν, τι προέκυψε) και πετυχαίνουμε να συμβαίνει όποτε θέλουμε. Ένα σφάλμα που δεν αναπαράγεται δεν μπορεί να ερευνηθεί, ούτε μπορούμε να βεβαιωθούμε ότι διορθώθηκε.
  2. Διατύπωση υπόθεσης. Ένας ισχυρισμός για την αιτία που μπορεί να αποδειχθεί ψευδής: «η ταξινόμηση δεν ταξινομεί», «ο δείκτης του δεύτερου κεντρικού δεδομένου είναι κατά ένα μεγαλύτερος».
  3. Σχεδιασμός πειράματος. Επιλέγουμε ένα σημείο του προγράμματος και προβλέπουμε ποια κατάσταση θα έπρεπε να φαίνεται εκεί αν η υπόθεση είναι αληθής και ποια αν είναι ψευδής.
  4. Συμπέρασμα και περιορισμός. Απορρίπτουμε την υπόθεση ή την επιβεβαιώνουμε· και στις δύο περιπτώσεις, το ύποπτο κομμάτι μικραίνει. Επαναλαμβάνουμε ώσπου το κομμάτι να γίνει μία γραμμή.

Ωστόσο, η μέθοδος απαιτεί πειθαρχία: αλλάζουμε ένα μόνο πράγμα κάθε φορά, σημειώνουμε κάθε υπόθεση με το αποτέλεσμά της, και δεν αγγίζουμε τον κώδικα όσο δεν ξέρουμε τι ψάχνουμε. Αν αλλάζουμε γραμμές στην τύχη ώσπου η έξοδος να φαίνεται σωστή, μπορεί να κρύψουμε την αστοχία χωρίς να αφαιρέσουμε το σφάλμα.

ΠαράδειγμαΗ διάμεσος με άρτιο πλήθος δεδομένων

Η διάμεσος μιας λίστας δεδομένων είναι το κεντρικό δεδομένο της ταξινομημένης λίστας αν το πλήθος είναι περιττό, και ο μέσος όρος των δύο κεντρικών αν είναι άρτιο. Το μάθημα Δοκιμές προγραμμάτων έδειξε ότι οι δοκιμές ανακαλύπτουν πότε αστοχεί μια συνάρτηση, αλλά όχι πού βρίσκεται το σφάλμα της. Η συνάρτηση που ακολουθεί περνά τη δοκιμή με πέντε δεδομένα και όχι με τέσσερα.

Πρόγραμμα σε C++
#include <algorithm>#include <cstddef>#include <print>#include <vector>​// Διάμεσος μιας λίστας δεδομένων.// Προσυνθήκη: το values δεν είναι κενό.// Μετασυνθήκη: επιστρέφει το κεντρικό δεδομένο της ταξινομημένης λίστας ή τον μέσο όρο των δύο κεντρικών.double median(std::vector<double> values){    std::ranges::sort(values);    const std::size_t n{values.size()};    const std::size_t middle{n / 2};    if (n % 2 == 1) {        return values[middle];    }    return (values[middle] + values[middle + 1]) / 2.0;}​int main(){    const std::vector<double> odd{7.0, 1.0, 5.0, 3.0, 9.0};    const std::vector<double> even{7.0, 1.0, 5.0, 3.0};    std::println("Διάμεσος του {}: {}", odd, median(odd));    std::println("Διάμεσος του {}: {}", even, median(even));}
Έξοδος
Διάμεσος του [7, 1, 5, 3, 9]: 5Διάμεσος του [7, 1, 5, 3]: 6

Η παρατήρηση είναι ακριβής: με {7,1,5,3}\{7, 1, 5, 3\}, ταξινομημένα {1,3,5,7}\{1, 3, 5, 7\}, αναμενόταν (3+5)/2=4(3 + 5)/2 = 4 και προέκυψε 6· με πέντε δεδομένα, το αποτέλεσμα είναι σωστό. Χωρούν τουλάχιστον τρεις υποθέσεις: (H1) η ταξινόμηση δεν ταξινομεί· (H2) οι δείκτες των δύο κεντρικών δεδομένων είναι λάθος· (H3) το άθροισμα ή η διαίρεση είναι γραμμένα λάθος. Καθεμιά προβλέπει κάτι διαφορετικό στη γραμμή 17, πριν εκτελεστεί: αν η H1 είναι αληθής, το values δεν είναι ταξινομημένο· αν είναι η H2, τα values[middle] και values[middle + 1] δεν είναι 3 και 5· αν είναι η H3, τα δύο δεδομένα είναι 3 και 5 και παρ’ όλα αυτά ο λογαριασμός δίνει 6. Το ότι η περιττή περίπτωση λειτουργεί κάνει την H1 απίθανη, αλλά «απίθανη» δεν σημαίνει «απορριφθείσα»· το πείραμα, με τον αποσφαλματωτή, είναι να σταματήσουμε το πρόγραμμα στη γραμμή 17 και να κοιτάξουμε.

Η διχοτόμηση

Όταν δεν υπάρχει σαφής υπόθεση, μένει μια μέθοδος που δεν αποτυγχάνει: η διχοτόμηση. Υποθέτουμε ότι η κατάσταση του προγράμματος είναι σωστή στην αρχή και λάθος στο τέλος, και ότι σε κάθε ενδιάμεσο σημείο μπορούμε να αποφασίσουμε αν είναι σωστή (συγκρίνοντάς την με μια τιμή υπολογισμένη με το χέρι ή ελέγχοντας μια αναλλοίωτη). Εξετάζουμε την κατάσταση στη μέση του ύποπτου κομματιού: αν είναι σωστή, το σφάλμα βρίσκεται στο δεύτερο μισό· αν όχι, στο πρώτο. Είναι η δυαδική αναζήτηση του μαθήματος Αναζήτηση και ταξινόμηση, εφαρμοσμένη στις γραμμές ενός προγράμματος (ή στους γύρους ενός βρόχου, ή στις εκδόσεις ενός αρχείου).

Το σχήμα παρακολουθεί τη διχοτόμηση δεκαέξι γραμμών με το σφάλμα στην 11· κάθε σημάδι είναι ένα πείραμα πάνω στην κατάσταση μετά από εκείνη τη γραμμή: σωστή (✓) ή λάθος (✗).

Απόδειξη

  1. Στην αρχή, και οι δεκαέξι γραμμές είναι ύποπτες: ξέρουμε ότι η κατάσταση είναι σωστή πριν από τη γραμμή 1 και λάθος μετά τη 16.

  2. Μετά τη γραμμή 8, η κατάσταση είναι σωστή: οι γραμμές 1 έως 8 απαλλάσσονται.

  3. Μετά τη 12, η κατάσταση είναι ήδη λάθος: το σφάλμα βρίσκεται ανάμεσα στην 9 και τη 12.

  4. Μετά τη 10, σωστή: μένουν η 11 και η 12.

  5. Μετά την 11, λάθος: το τελευταίο σωστό σημείο και το πρώτο λάθος είναι γειτονικά, και το σφάλμα βρίσκεται στη γραμμή 11.

Η μέτρηση είναι εκείνη της δυαδικής αναζήτησης: κάθε πείραμα διαιρεί το κομμάτι στα δύο, έτσι ώστε kk πειράματα αρκούν για 2k2^k γραμμές. Για ένα πρόγραμμα χιλίων γραμμών αρκούν δέκα, επειδή 210=10242^{10} = 1024. Από όπου το πλεονέκτημα της μεθόδου: το κόστος της αυξάνεται όπως ο λογάριθμος του μεγέθους, και δεν απαιτεί να κατανοήσουμε όλο το πρόγραμμα, αλλά να ξέρουμε να αποφασίζουμε, σε ένα σημείο, αν η κατάσταση είναι η οφειλόμενη. Ωστόσο, αυτή η απόφαση είναι το δύσκολο μέρος: οι προδιαγραφές και οι αναλλοίωτες είναι το κριτήριο με το οποίο κρίνεται κάθε ενδιάμεση κατάσταση.