Όλοι γνωρίζουμε ότι η CPU και η μνήμη είναι οι δύο πιο σημαντικές μετρήσεις για ένα πρόγραμμα, οπότε πόσοι άνθρωποι έχουν σκεφτεί πραγματικά την ερώτηση: Πόσα byte καταλαμβάνει στη μνήμη μια παρουσία ενός τύπου (τύπος τιμής ή τύπος αναφοράς); Πολλοί από εμάς δεν μπορούμε να απαντήσουμε. Η C# παρέχει ορισμένους τελεστές και API για τον υπολογισμό μεγεθών, αλλά κανένας από αυτούς δεν λύνει πλήρως το πρόβλημα που μόλις ρώτησα. Αυτό το άρθρο παρέχει μια μέθοδο για τον υπολογισμό του αριθμού των byte μνήμης που καταλαμβάνονται από παρουσίες τύπων τιμών και τύπων αναφοράς. Η λήψη του πηγαίου κώδικα γίνεται από εδώ.
1. Μέγεθοςχειριστή 2. Marshal.SizeOf μέθοδος 3. Μη ασφαλής.ΜέγεθοςΤης μεθόδου > 4. Μπορεί να υπολογιστεί με βάση τον τύπο του μέλους πεδίου; 5. Διάταξη τύπων τιμών και τύπων εφαρμογών 6. Οδηγία LDFLDA 7. Υπολογίστε τον αριθμό των byte του τύπου τιμής 8. Μετρήστε τον αριθμό των byte του τύπου παραπομπής 9. Πλήρης υπολογισμός
1. Μέγεθοςχειριστή
Η λειτουργία sizeof χρησιμοποιείται για τον προσδιορισμό του αριθμού των byte που καταλαμβάνονται από μια παρουσία ενός τύπου, αλλά μπορεί να εφαρμοστεί μόνο σε μη διαχειριζόμενους τύπους. Ο λεγόμενος μη διαχειριζόμενος τύπος περιορίζεται σε:
Πρωτόγονοι τύποι: Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double και Single) Δεκαδικός τύπος Τύπος απαρίθμησης Τύπος δείκτη Δομές που περιέχουν μόνο μέλη δεδομένων τύπου Μη διαχειριζόμενες Όπως υποδηλώνει το όνομα, ένας μη διαχειριζόμενος τύπος είναι ένας τύπος τιμής και η αντίστοιχη παρουσία δεν μπορεί να περιέχει καμία αναφορά στο διαχειριζόμενο αντικείμενο. Εάν ορίσουμε μια γενική μέθοδο όπως αυτή για να καλέσουμε τον τελεστή sizeof, η γενική παράμετρος T πρέπει να προσθέσει έναν μη διαχειρισμένο περιορισμό και μια μη ασφαλή ετικέτα στη μέθοδο.
Μόνο οι εγγενείς τύποι και οι τύποι απαρίθμησης μπορούν να χρησιμοποιήσουν απευθείας τον τελεστή sizeof, ο οποίος πρέπει να προστεθεί εάν εφαρμοστεί σε άλλους τύπους (δείκτες και προσαρμοσμένες δομές)./unsafeμεταγλώττισης και πρέπει επίσης να τοποθετηθούν σεεπικίνδυνοςστο πλαίσιο.
Δεδομένου ότι η ακόλουθη δομή Foobar δεν είναι μη διαχειριζόμενος τύπος, το πρόγραμμα θα έχει σφάλμα μεταγλώττισης.
2. Marshal.SizeOf μέθοδος
Στατικοί τύποι Το Marshal ορίζει μια σειρά από API που μας βοηθούν να εκχωρήσουμε και να αντιγράψουμε μη διαχειριζόμενη μνήμη, να μετατρέψουμε μεταξύ διαχειριζόμενων και μη διαχειριζόμενων τύπων και να εκτελέσουμε μια σειρά από άλλες λειτουργίες σε μη διαχειριζόμενη μνήμη (το Marshal στην υπολογιστική επιστήμη αναφέρεται στη λειτουργία μετατροπής αντικειμένων μνήμης στην αντίστοιχη μορφή αποθήκευσης ή μεταφοράς δεδομένων). Στατικό, το οποίο περιλαμβάνει τις ακόλουθες 4 υπερφορτώσεις της μεθόδου SizeOf για τον προσδιορισμό του αριθμού των byte ενός δεδομένου τύπου ή αντικειμένου.
Η μέθοδος Marshal.SizeOf δεν έχει περιορισμό στον καθορισμένο τύπο για τον μη διαχειριζόμενο τύπο, αλλά εξακολουθεί να απαιτεί τον καθορισμό ενόςΤύπος τιμής。 Εάν το εισερχόμενο αντικείμενο είναι αντικείμενο, πρέπει επίσης να είναι ένα πλαίσιο για έναν τύπο τιμής.
Δεδομένου ότι το ακόλουθο Foobar ορίζεται ως:είδος, επομένως οι κλήσεις και στις δύο μεθόδους SizeOf θα δημιουργήσουν μια εξαίρεση ArgumentException και μια προτροπή: Ο τύπος 'Foobar' δεν μπορεί να ομαδοποιηθεί ως μη διαχειριζόμενη δομή. Δεν μπορεί να υπολογιστεί σημαντικό μέγεθος ή μετατόπιση.
Marshal.ΜέγεθοςΤης μεθόδουΤα γενόσημα δεν υποστηρίζονται, αλλά έχει και απαιτήσεις για τη διάταξη της κατασκευής, η οποία υποστηρίζει στήριξηΑλληλοδιάδοχοςκαιΡητόςΛειτουργία διάταξης. Δεδομένου ότι η δομή Foobar που φαίνεται παρακάτω υιοθετεί την κατάσταση αυτόματης διάταξης (Auto, η οποία δεν υποστηρίζει "δυναμικό σχεδιασμό" της διάταξης μνήμης με βάση τα μέλη του πεδίου λόγω των αυστηρότερων απαιτήσεων διάταξης μνήμης σε μη διαχειριζόμενα περιβάλλοντα), οι κλήσεις στη μέθοδο SizeOf θα εξακολουθούν να έχουν την ίδια εξαίρεση ArgumentException όπως παραπάνω.
3. Μέθοδος Unsafe.SizeOf
Το Static Unsafe παρέχει περισσότερες λειτουργίες χαμηλού επιπέδου για μη διαχειριζόμενη μνήμη και παρόμοιες μέθοδοι SizeIOf ορίζονται επίσης σε αυτόν τον τύπο. Η μέθοδος δεν έχει περιορισμούς στον τύπο που καθορίζεται, αλλά εάν καθορίσετε έναν τύπο αναφοράς, επιστρέφει τοΑριθμός byte δείκτη"(IntPtr.Size)。
4. Μπορεί να υπολογιστεί με βάση τον τύπο του μέλους πεδίου;
Γνωρίζουμε ότι τόσο οι τύποι τιμών όσο και οι τύποι αναφοράς αντιστοιχίζονται ως συνεχές τμήμα (ή αποθηκεύονται απευθείας σε έναν καταχωρητή). Ο σκοπός ενός τύπου είναι να καθορίσει τη διάταξη μνήμης ενός αντικειμένου και οι παρουσίες του ίδιου τύπου έχουν την ίδια διάταξη και ο αριθμός των byte είναι φυσικά ο ίδιος (για πεδία τύπου αναφοράς, αποθηκεύει μόνο τη διεύθυνση αναφοράς σε αυτήν την ακολουθία byte). Δεδομένου ότι το μήκος byte καθορίζεται από τον τύπο, εάν μπορούμε να προσδιορίσουμε τον τύπο κάθε μέλους πεδίου, δεν θα μπορούσαμε να υπολογίσουμε τον αριθμό των byte που αντιστοιχούν σε αυτόν τον τύπο; Στην πραγματικότητα, δεν είναι δυνατόν.
Για παράδειγμα, γνωρίζουμε ότι τα byte byte, short, int και long είναι 1, 2, 4 και 8, επομένως ο αριθμός των byte για ένα δυαδικό byte είναι 2, αλλά για έναν συνδυασμό τύπου byte + short, byte + int και byte + long, τα αντίστοιχα byte δεν είναι 3, 5 και 9, αλλά 3, 8 και 16. Επειδή αυτό περιλαμβάνει το ζήτημα της ευθυγράμμισης της μνήμης.
5. Διάταξη των τύπων τιμών και των τύπων αναφοράς
Ο αριθμός των byte που καταλαμβάνουν οι παρουσίες του τύπου αναφοράς και του υποτύπου είναι επίσης διαφορετικός για το ίδιο ακριβώς μέλος δεδομένων. Όπως φαίνεται στην παρακάτω εικόνα, η ακολουθία byte της παρουσίας τύπου τιμήςΌλα είναι μέλη πεδίου που χρησιμοποιούνται για την αποθήκευσή του。 Για περιπτώσεις τύπων αναφοράς, η διεύθυνση του αντίστοιχου πίνακα μεθόδου τύπου αποθηκεύεται επίσης μπροστά από την ακολουθία byte πεδίου. Ο πίνακας μεθόδου παρέχει σχεδόν όλα τα μετα-δεδομένα που περιγράφουν τον τύπο και χρησιμοποιούμε αυτήν την αναφορά για να προσδιορίσουμε σε ποιον τύπο ανήκει η παρουσία. Στο μπροστινό μέρος, υπάρχουν επίσης επιπλέον byte, τα οποία θα ονομάσουμεΚεφαλίδα αντικειμένουΔεν χρησιμοποιείται μόνο για την αποθήκευση της κλειδωμένης κατάστασης του αντικειμένου, αλλά η τιμή κατακερματισμού μπορεί επίσης να αποθηκευτεί προσωρινά εδώ. Όταν δημιουργούμε μια μεταβλητή τύπου αναφοράς, αυτή η μεταβλητήΔεν δείχνει το πρώτο byte μνήμης που καταλαμβάνει το στιγμιότυπο, αλλά το μέρος όπου είναι αποθηκευμένη η διεύθυνση του πίνακα μεθόδου。
6. Οδηγία LDFLDA
Όπως παρουσιάσαμε παραπάνω, ο τελεστής sizeof και η μέθοδος SizeOf που παρέχονται από τον στατικό τύπο Marshal/Unsafe δεν μπορούν πραγματικά να λύσουν τον υπολογισμό του μήκους byte που καταλαμβάνουν οι περιπτώσεις. Από όσο γνωρίζω, αυτό το πρόβλημα δεν μπορεί να λυθεί μόνο στο πεδίο C#, αλλά παρέχεται σε επίπεδο ILLdfldaΟι οδηγίες μπορούν να μας βοηθήσουν να λύσουμε αυτό το πρόβλημα. Όπως υποδηλώνει το όνομα, το Ldflda σημαίνει Load Field Address, το οποίο μας βοηθά να λάβουμε τη διεύθυνση ενός πεδίου στην περίπτωση. Δεδομένου ότι αυτή η εντολή IL δεν έχει αντίστοιχο API στη C#, μπορούμε να τη χρησιμοποιήσουμε μόνο στην ακόλουθη μορφή χρησιμοποιώντας το IL Emit.
Όπως φαίνεται στο παραπάνω απόσπασμα κώδικα, έχουμε μια μέθοδο GenerateFieldAddressAccessor στον τύπο SizeCalculator, η οποία δημιουργεί έναν εκπρόσωπο τύπου Func<object?, long[]> με βάση τη λίστα πεδίων του καθορισμένου τύπου, η οποία μας βοηθά να επιστρέψουμε τη διεύθυνση μνήμης του καθορισμένου αντικειμένου και όλων των πεδίων του. Με τη διεύθυνση του ίδιου του αντικειμένου και τη διεύθυνση κάθε πεδίου, μπορούμε φυσικά να λάβουμε τη μετατόπιση κάθε πεδίου και στη συνέχεια να υπολογίσουμε εύκολα τον αριθμό των byte μνήμης που καταλαμβάνει ολόκληρη η παρουσία.
7. Υπολογίστε τον αριθμό των byte του τύπου τιμής
Δεδομένου ότι οι τύποι τιμών και οι τύποι αναφοράς έχουν διαφορετικές διατάξεις στη μνήμη, πρέπει επίσης να χρησιμοποιήσουμε διαφορετικούς υπολογισμούς. Δεδομένου ότι το byte της δομής είναι το περιεχόμενο όλων των πεδίων στη μνήμη, χρησιμοποιούμε έναν έξυπνο τρόπο για να το υπολογίσουμε. Ας υποθέσουμε ότι πρέπει να διευθετήσουμε τον αριθμό των byte μιας δομής τύπου T, τότε δημιουργούμε μια πλειάδα ValueTuple<T,T> και η μετατόπιση του δεύτερου πεδίου της Item2 είναι ο αριθμός των byte της δομής T. Η συγκεκριμένη μέθοδος υπολογισμού αντικατοπτρίζεται στην ακόλουθη μέθοδο CalculateValueTypeInstance.
Όπως φαίνεται στο παραπάνω απόσπασμα κώδικα, υποθέτοντας ότι ο τύπος δομής που πρέπει να υπολογίσουμε είναι T, καλούμε τη μέθοδο GetDefaultAsObject για να λάβουμε το αντικείμενο default(T) με τη μορφή ανάκλασης και, στη συνέχεια, δημιουργούμε μια πλειάδα ValueTuple<T,T>tuple. Αφού καλέσουμε τη μέθοδο GenerateFieldAddressAccessor για να λάβουμε τον πληρεξούσιο Func<object?, long[]> για τον υπολογισμό της παρουσίας και των διευθύνσεων πεδίων της, καλούμε αυτόν τον πληρεξούσιο ως όρισμα. Για τις τρεις διευθύνσεις μνήμης που παίρνουμε, η πλειάδα κώδικα και οι διευθύνσεις των πεδίων 1 και 2 είναι ίδιες, χρησιμοποιούμε την τρίτη διεύθυνση που αντιπροσωπεύει το Στοιχείο2 μείον την πρώτη διεύθυνση και παίρνουμε το αποτέλεσμα που θέλουμε.
8. Μετρήστε τον αριθμό των byte του τύπου παραπομπής
Ο υπολογισμός byte για τύπους αναφοράς είναι πιο περίπλοκος, χρησιμοποιώντας αυτήν την ιδέα: αφού λάβουμε τη διεύθυνση της ίδιας της παρουσίας και κάθε πεδίου, ταξινομούμε τις διευθύνσεις για να λάβουμε τη μετατόπιση του τελευταίου πεδίου. Ας προσθέσουμε αυτή τη μετατόπιση στον αριθμό των byte του ίδιου του τελευταίου πεδίου και, στη συνέχεια, ας προσθέσουμε τα απαραίτητα "πρώτα και τελευταία byte" στο αποτέλεσμα που θέλουμε, το οποίο αντικατοπτρίζεται στην ακόλουθη μέθοδο CalculateReferneceTypeInstance.
Όπως φαίνεται στο παραπάνω απόσπασμα κώδικα, εάν ο καθορισμένος τύπος δεν έχει καθορισμένα πεδία, το CalculateReferneceTypeInstance επιστρέφει τον ελάχιστο αριθμό byte της παρουσίας τύπου αναφοράς: 3 φορές τον αριθμό των byte δείκτη διεύθυνσης. Για αρχιτεκτονικές x86, ένα αντικείμενο τύπου εφαρμογής καταλαμβάνει τουλάχιστον 12 byte, συμπεριλαμβανομένου του ObjectHeader (4 byte), δεικτών πίνακα μεθόδων (byte) και τουλάχιστον 4 byte περιεχομένου πεδίου (αυτά τα 4 byte απαιτούνται ακόμη και αν δεν έχει οριστεί τύπος χωρίς πεδία). Στην περίπτωση της αρχιτεκτονικής x64, αυτός ο ελάχιστος αριθμός byte θα είναι 24, επειδή ο δείκτης του πίνακα μεθόδου και το ελάχιστο περιεχόμενο πεδίου θα γίνουν 8 byte, αν και το έγκυρο περιεχόμενο του ObjectHeader καταλαμβάνει μόνο 4 byte, αλλά 4 byte αναπλήρωσης θα προστεθούν μπροστά.
Η διευθέτηση των byte που καταλαμβάνονται από το τελευταίο πεδίο είναι επίσης πολύ απλή: εάν ο τύπος είναι τύπος τιμής, τότε η μέθοδος CalculateValueTypeInstance που ορίστηκε προηγουμένως καλείται να υπολογίσει, εάν είναι τύπος αναφοράς, το περιεχόμενο που είναι αποθηκευμένο στο πεδίο είναι μόνο η διεύθυνση μνήμης του αντικειμένου προορισμού, επομένως το μήκος είναι IntPtr.Size. Δεδομένου ότι οι περιπτώσεις τύπου αναφοράς ευθυγραμμίζονται με το IntPtr.Size στη μνήμη από προεπιλογή, αυτό γίνεται και εδώ. Τέλος, μην ξεχνάτε ότι η αναφορά της παρουσίας τύπου αναφοράς δεν δείχνει στο πρώτο byte μνήμης, αλλά στο byte που αποθηκεύει τον δείκτη του πίνακα μεθόδου, επομένως πρέπει να προσθέσετε τον αριθμό των byte του ObjecthHeader (IntPtr.Size).
9. Πλήρης υπολογισμός
Οι δύο μέθοδοι που χρησιμοποιούνται για τον υπολογισμό του αριθμού των byte των παρουσιών τύπου τιμής και τύπου αναφοράς χρησιμοποιούνται στην ακόλουθη μέθοδο SizeOf. Δεδομένου ότι η κλήση της εντολής Ldflda πρέπει να παρέχει μια αντίστοιχη παρουσία, αυτή η μέθοδος παρέχει έναν εκπρόσωπο για να αποκτήσει την αντίστοιχη παρουσία εκτός από την παροχή του τύπου προορισμού. Οι παράμετροι που αντιστοιχούν σε αυτόν τον εκπρόσωπο μπορούν να προεπιλεγούν και θα χρησιμοποιήσουμε την προεπιλεγμένη τιμή για τον τύπο τιμής. Για τύπους αναφοράς, θα προσπαθήσουμε επίσης να δημιουργήσουμε το αντικείμενο-στόχο χρησιμοποιώντας τον προεπιλεγμένο κατασκευαστή. Εάν αυτό το αντικείμενο πληρεξουσίου δεν παρέχεται και η παρουσία προορισμού δεν μπορεί να δημιουργηθεί, η μέθοδος SizeOf δημιουργεί μια εξαίρεση. Αν και πρέπει να παρέχουμε την παρουσία προορισμού, το υπολογισμένο αποτέλεσμα σχετίζεται μόνο με τον τύπο, επομένως αποθηκεύουμε προσωρινά το υπολογισμένο αποτέλεσμα. Για ευκολία στην κλήση, παρέχουμε επίσης μια άλλη γενική μέθοδο SizeOf<T>.
Στο παρακάτω απόσπασμα κώδικα, το χρησιμοποιούμε για να εξάγουμε τον αριθμό των byte δύο δομών και τύπων με τον ίδιο ορισμό πεδίου. Στο επόμενο άρθρο, θα λάβουμε περαιτέρω το πλήρες δυαδικό περιεχόμενο της παρουσίας στη μνήμη με βάση τον υπολογισμένο αριθμό byte, οπότε μείνετε συντονισμένοι.
Αρχικός σύνδεσμος:Η σύνδεση με υπερσύνδεσμο είναι ορατή. |