Java vs C++: Trading UB για διαρροές σημασιολογικής μνήμης (Ίδιο πρόβλημα, διαφορετική τιμωρία για αποτυχία)

0
Java vs C++: Trading UB για διαρροές σημασιολογικής μνήμης (Ίδιο πρόβλημα, διαφορετική τιμωρία για αποτυχία)
Λαγός «Χωρίς σφάλματα». Συγγραφέας: Λαγός «Χωρίς σφάλματα». Ακολουθηστε: ΚελάδημαFacebook
Τίτλος εργασίας: Σαρκαστικός Αρχιτέκτονας
Χόμπι: Σκέψη φωναχτά, Διαφωνία με Διευθυντές, Ενοχλητικά HR,
Αποκαλώντας ένα Μπαστούνι, Κρατώντας τη γλώσσα στο μάγουλο
C++ vs Java: UB vs Semantic Memory Leaks

Για πολύ καιρό, αρκετοί άνθρωποι (κυρίως από ομάδες προγραμματισμού ακαδημιών ή/και Java) πίστευαν πιστά σε μια φρικτή εσφαλμένη αντίληψη σύμφωνα με το «τα προγράμματα που συλλέγονται από σκουπίδια δεν είναι δυνατό να διαρρεύσουν μνήμης» (ή τουλάχιστον σύμφωνα με τις γραμμές του «Είναι θεμελιωδώς πιο δύσκολο να υπάρχει διαρροή μνήμης στο πρόγραμμα που συλλέγει τα σκουπίδια», το οποίο το κοινό μεταφράζεται εύκολα στο πρώτο) [GC-FAQ][C2-GC]. Αυτό συμβαίνει παρά τα θέματα που σχετίζονται με διαρροές μνήμης στην Java, τα οποία συζητήθηκαν τουλάχιστον ήδη από το 1999 [Lycklama99]και συχνά συζητούνται περίπου στο ίδιο σημείο με την λανθασμένη αντίληψη παραπάνω [C2-MemoryLeaksGC].

Ωστόσο, η πραγματικότητα των {περισσότερων|αρκετών|μερικών}1 προγράμματα Java του πραγματικού κόσμου φρικτός αναμνηστικά με την πάροδο του χρόνου, χτυπούσε την πόρτα όλο και πιο επίμονα, και μέχρι το 2017 τουλάχιστον οι ηγέτες της κοινής γνώμης κατέληξαν στο συμπέρασμα ότι[Sor17][Paraschiv17][Java8docs.MemLeaks][etc. etc. etc.]

ΥΠΑΡΧΟΥΝ διαρροές μνήμης στην Java


1 επιλέξτε ένα ανάλογα με την κατασκήνωση στην οποία βρίσκεστε, αλλά μην ξεχνάτε το Eclipse και το OpenHAB

Διαρροές Συντακτικής έναντι Σημασιολογικής Μνήμης

Το πρόβλημα με την παραπάνω εσφαλμένη αντίληψη προέρχεται από μια λεπτή διαφορά μεταξύ αυτού που είναι γνωστό ως «διαρροές συντακτικής μνήμης» και «διαρροές σημασιολογικής μνήμης» (που ονομάζεται «loiterers» στο [Lycklama99]). Σίγουρα, οποιοσδήποτε ημιαξιοπρεπής σκουπιδοσυλλέκτης θα το διασφαλίσει τα απρόσιτα αντικείμενα καθαρίζονται2; Ωστόσο, ενώ όλα τα απρόσιτα αντικείμενα είναι άχρηστα,

δεν είναι όλα τα άχρηστα αντικείμενα απρόσιτα

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

Μέχρι εδώ όλα καλά, αλλά τώρα πρέπει να το παρατηρήσουμε από την άποψη του τελικός χρήστης του προγράμματος, δεν με ενδιαφέρει απρόσιτο – καθόλου; Αντίθετα, αυτό που με ενδιαφέρει είναι το πρόγραμμα δεν μπαίνει σε swap μετά από μισή μέρα χρήσης; όπως δείχνει η πρακτική – ακόμη και με όλα τα απρόσιτα αντικείμενα που αφαιρούνται (δηλαδή ακόμα κι αν δεν υπάρχουν διαρροές συντακτικής μνήμης)αυτά διαρροές σημασιολογικής μνήμης μπορεί εύκολα να προκαλέσει αυτή τη φοβερή ανταλλαγή.


2 στην πραγματικότητα, είναι «είναι τελικά καθαρίστε», αλλά με ένα αληθινό πνεύμα να είμαστε ευγενικοί με αυτούς που ήδη υποφέρουν, θα το ξεχάσουμε αυτό τελικά λέξη προς το παρόν

Σημασιολογικές διαρροές μνήμης σε Java

Υπάρχουν αρκετά κοινά σενάρια για το πώς μπορούν να εμφανιστούν διαρροές μνήμης στην Java (βλ., για παράδειγμα, ταξινόμηση σε [Lycklama99]), αλλά τα περισσότερα από αυτά3 συνοψίζονται είτε στο να ξεχάσετε να αφαιρέσετε μια αναφορά σε ένα αντικείμενο από κάποια συλλογή είτε στο να ξεχάσετε να ορίσετε μια αναφορά που δεν χρειάζεται πλέον μηδενικό. Πράγματι, αν κρατάμε κάτι-άχρηστο μέσα σε μια συλλογή, ή είναι κρατώντας μια αναφορά σε ένα αντικείμενο που δεν είναι πλέον απαραίτητο χωρίς καμία πιθανότητα να χρησιμοποιήσουμε ξανά αυτήν την αναφορά – έχουμε α διαρροή σημασιολογικής μνήμης.

Κρίνοντας λαγός:με κάθε ασέβεια στα μεταβλητά στατικά/παγκόσμια δεδομένα, πρέπει να πω ότι το πρόβλημα του διαρροές σημασιολογικής μνήμης ΔΕΝ περιορίζεται στα στατικάΜερικοί συγγραφείς τείνουν να υπεραπλουστεύουν το τελευταίο πρόβλημα σε κάτι σαν „Γεια σου, ας είμαστε προσεκτικοί με το mutable στατικός δεδομένα“; Ωστόσο, με κάθε ασέβεια προς τα μεταβλητά στατικά/παγκόσμια δεδομένα (ναι, αυτό περιλαμβάνει τα singleton), πρέπει να πω ότι το πρόβλημα διαρροές σημασιολογικής μνήμης ΔΕΝ περιορίζεται στη στατική (στην πραγματικότητα, η στατική είναι απλώς α ειδική περίπτωση της υπάρχουσας αλλά μη χρησιμοποιημένης αναφοράς). Για παράδειγμα, ακόμα κι αν βάλω τη μη μηδενισμένη αναφορά στη στοίβα, Δεν θα κυκλοφορήσει μέχρι να ξεπεράσω αυτό ακριβώς το σημείο στη στοίβα – το οποίο, ανάλογα με την εφαρμογή, μπορεί εύκολα να διαρκέσει αρκετά μέχρι το θάνατο της εφαρμογής θα χωρίσουμε.

Ένα τέτοιο παράδειγμα είναι ένα αντικείμενο με μια αναφορά που διατηρείται από κύριος() λειτουργία. Γενικότερα – μόλις έχουμε οποιοδήποτε είδος βρόχου ανώτατου επιπέδου – όπως βρόχο συμβάντων – τότε όλα τα αντικείμενα που κρατούνται για εμάς από τον βρόχο συμβάντων, συμπεριλαμβανομένων όλων των αντικειμένων που είναι προσβάσιμα μέσω αναφορών που προέρχονται από οποιοδήποτε από αυτά τα αντικείμεναΠΡΕΠΕΙ να μηδενιστούν οι αναφορές τους με μη αυτόματο τρόπο για να αποφευχθεί η μετατροπή τέτοιων αναφορών διαρροές σημασιολογικής μνήμης.


3 εξοικονόμηση για ιδιαιτερότητες του JVM ή εσωτερικά πράγματα όπως ClassLoaderμικρό

Τι γίνεται με το C/C++;

Έτσι, στην Java, για να αποφύγουμε διαρροές σημασιολογικής μνήμης, πρέπει να χρησιμοποιήσουμε x = μηδενικό; για αποφυγή διαρροών μνήμης. Αλλά αυτό είναι ένα ακριβές ισοδύναμο της ρητής διαγράφω που έχουν να κάνουν σε C/C++(!), έστω και για διαφορετικό λόγο (για να αποφευχθούν οι κρεμαστές δείκτες)!

Ας συγκρίνουμε τα ακόλουθα τρία κομμάτια κώδικα:


//pre-C++11 C++
struct State {
  uint8_t* data;

  void addData() {
    data = new uint8_t[1000000];
    //do something with data
  }
  void removeData() {
    delete [] data;
    data = nullptr;//(*)
  }
  ~State() {
    delete [] data;
  }
};

//post-C++11 C++
struct State {
  std::unique_ptr<uint8_t[]> data;

  void addData() {
    data = make_unique<uint8_t[]>(1'000'000);
    //do something with data
  }
  void removeData() {
    data.reset();//(*)
  }
};
 
//JAVA
class State {
  byte[] data;

  void addData() {
    data = new byte[1000000];
    //do something with data
  }
  void removeData() {
    data = null;//(*) 
  } 
}; 

Από την τρέχουσα οπτική μου, αυτά τα τρία κομμάτια κώδικα είναι σημασιολογικά ταυτόσημη (δηλαδή η μόνη διαφορά αφορά τη σύνταξη – η οποία είναι η TBH δεν είναι και πολύ διαφορετική).

Είναι πραγματικά πανομοιότυπα; Λοιπόν, όχι ακριβώς…

Παρά ταύτα εντυπωσιακές ομοιότητες μεταξύ αυτού που μπορεί να θεωρηθεί ως „ασφαλής και χωρίς διαρροή μνήμης κώδικα“ σε δύο υποτιθέμενες πολύ διαφορετικές γλώσσες προγραμματισμού από αυτήν την άποψηυπάρχει ακόμα μια σημαντική διαφορά.

Συγκεκριμένα, αν ξεχάσουμε να αναθέσουμε μηδενικό προς την δεδομένα στη γραμμή σημειώνονται με (ή να καλέσετε επαναφορά()

  • για μετά τη C++11 C++), τα εφέ θα είναι διαφορετικά:Λαγός με πρόσωπο omg:Η C++ τιμωρεί για πρόσβαση σε ήδη διαγραμμένα δεδομένα με απροσδιόριστη συμπεριφορά (UB)
  • pre-C++11 C++ τιμωρεί για την πρόσβαση σε ήδη διαγραμμένα δεδομένα με Απροσδιόριστη Συμπεριφορά (UB) – που σε αυτή την περίπτωση θα μεταφραστεί στην καλύτερη περίπτωση σε συντριβή <ωχ! />, και στη χειρότερη – σε αλλοιωμένα δεδομένα Η Java είναι σημαντικά πιο επιεική από αυτή την άποψη και ξεχασμένη δεδομένα = μηδενικό
    • τιμωρείται μόνο με τη διαρροή σημασιολογικής μνήμης.
    • OTOH, είναι αυτή η επιείκεια που οδηγεί στο να είναι πανταχού παρόντα προγράμματα Java με διαρροές σημασιολογικής μνήμης: ένα πρόγραμμα C++ που διακόπτεται είναι ένα προφανές σφάλμα που είναι πολύ πιο πιθανό να διορθωθεί από το πρόγραμμα Java με διαρροή σημασιολογικής μνήμης (μεταξύ άλλων, διαρροές μνήμης συχνά δεν είναι προφανείς έως ότου κάποιος εκτελέσει το πρόγραμμα για πολλές ώρες – κάτι που μπορεί να αγνοηθεί στις περισσότερες από τις συνήθεις δοκιμές 🙁 ). Επιπλέον, στην Java υπάρχει μια ευκαιρία να έχετε ένα παράδειγμα κάποια άλλη τάξη να αναφερθώ δεδομένα ακόμα και αφού το ακυρώσαμε εδώ. Από ότι είδα, τέτοια κρυφές αναφορές είναι ένα μείζων πηγή του διαρροές σημασιολογικής μνήμης
  • σε πολύπλοκα προγράμματα Java του πραγματικού κόσμου.
    • Η C++ μετά τη C++11 συμπεριφέρεται πολύ περισσότερο σαν Java από αυτή την άποψη. Εξακολουθεί να είναι αρκετά διαφορετικό από την Java επειδή της C++ unique_ptr<> είναι εγγυημένη να είναι η μόνη αναφορά στο δεδομένα αντικείμενο. Αυτό, με τη σειρά του, εξαλείφει αυτά που μοιάζουν με Javaκρυφές αναφορές και με τη σειρά του μειώνει σημαντικά τις πιθανότητες να έχουμε α διαρροή σημασιολογικής μνήμης. Ωστόσο, υπό C++ ένα τέτοιο κρυφή αναφορά θα γίνει ακρεμασμένος δείκτης

, προκαλώντας για άλλη μια φορά φοβερή καταστροφή UB/crash/memory <ωχ! />.

Περίληψη

  • Προσπαθώ να συνοψίσω τη φασαρία μου παραπάνω:
    • Ο κώδικας που μπορεί να θεωρηθεί «καλός» όσον αφορά τη μνήμη (είναι ασφαλής τόσο από σφάλματα όσο και από διαρροές μνήμης) είναι εντυπωσιακά παρόμοιος με τη C++ και την Java. Ναι, αντίθετα με ό,τι μας λένε πολλά βιβλία, ακόμα και όταν προγραμματίζουμε σε Java, πρέπει να σκεφτόμαστε τη διαχείριση της μνήμης (ρε, μπορεί κανείς να ισχυριστεί ότι δεδομένα = μηδενικό
  • IS χειροκίνητη διαχείριση μνήμης). Ωστόσο, ΑΝ παρεκκλίνουμε από τέτοιες «καλές» πρακτικές κώδικα, διαφορετικές γλώσσες προγραμματισμού θα μας τιμωρούν διαφορετικά (στην C++ μπορεί να υπάρχει σφάλμα ή καταστροφή μνήμης, στη Java μπορεί να είναιδιαρροή σημασιολογικής μνήμης
  • ).Λαγός που μαλώνει:Η σημασιολογία του καλού κώδικα είναι περίπου η ίδια ανεξάρτητα από την επιλογή Java/C++.
    • Με άλλα λόγια, όταν μετακινούμαστε από τη C++ στην Java, ανταλλάσσουμε σφάλματα για διαρροές μνήμης. OTOH, καθώς οι διαρροές μνήμης δεν είναι τόσο εμφανείς όσο τα σφάλματα, έχουν την τάση να επιβιώνουν περισσότερο (συχνά ΠΟΛΥ περισσότερο). Με άλλα λόγια, όταν μετακινούμαστε από τη C++ στην Java, τείνουμε να ανταλλάσσουμε ΛΙΓΑ crashes με ΠΟΛΛΕΣ διαρροές μνήμης. που είναι BTW τείνει να είναι συνεπής με οποιαδήποτε προσωπική εμπειρία / ανέκδοτο στοιχείο έχω. Δεν πρόκειται να υποστηρίξω αν πρόκειται για καλό συμβιβασμό ή όχι. αυτό που είναι πιο σημαντικό το IMNSHO είναι αυτό σημασιολογία του Ο καλός κώδικας είναι περίπου ο ίδιος ανεξάρτητα από την επιλογή Java/C++.

Είπα.

ΑναγνώρισηΚινούμενα σχέδια του Sergey Gordeev IRL απόGordeev Animation Graphics

Πράγα.

Schreibe einen Kommentar