Απελευθερώστε τη δυναμική μνήμη C. Στατική και δυναμική κατανομή μνήμης. Δημιουργία δυναμικών συστοιχιών

Η εργασία με τη δυναμική μνήμη είναι συχνά μια συμφόρηση σε πολλούς αλγορίθμους, αν δεν εφαρμόζουν ειδικά κόλπα.

Στο άρθρο, θα εξετάσω μερικές τέτοιες τεχνικές. Παραδείγματα στο άρθρο είναι διαφορετικές (για παράδειγμα, από αυτό) από το γεγονός ότι χρησιμοποιείται η υπερφόρτωση των νέων και διαγραφών χειριστών και λόγω αυτού, οι δομές σύνταξης θα είναι μινιμαλιστικές και το remake του προγράμματος είναι απλό. Περιγράφονται επίσης υποβρύχιες πέτρες που βρέθηκαν στη διαδικασία (φυσικά, ο Γκουρού, ο οποίος διαβάζει το πρότυπο από την κρούστα για να ξεφλουδίσει, δεν θα εκπλαγεί).

0. Χρειαζόμαστε χειροκίνητη μνήμη με μνήμη;

Πρώτα απ 'όλα, ελέγξτε πώς ένας έξυπνος παραχωρητής μπορεί να επιταχύνει την εργασία με τη μνήμη.

Θα γράψουμε απλές δοκιμές για C ++ και C # (C # είναι γνωστό για έναν εξαιρετικό διαχειριστή μνήμης, το οποίο διαιρεί τα αντικείμενα παραγωγής, χρησιμοποιεί διαφορετικές πισίνες για αντικείμενα διαφορετικών μεγεθών κλπ.).

Κόμβος κλάσης (κοινό: κόμβος * επόμενη;); // ... για (int i \u003d 0; i< 10000000; i++) { Node* v = new Node(); }

Τάξη κόμβου (δημόσιος κόμβος δίπλα;) // ... για (int l \u003d 0, l< 10000000; l++) { var v = new Node(); }

Παρά το σύνολο του "σφαιρικού-κενού" του παραδείγματος, η διαφορά εγκαίρως αποδείχθηκε 10 φορές (62 ms έναντι 650 ms). Επιπλέον, C # έχει ολοκληρωθεί, και σύμφωνα με τους κανόνες της ορθής τόνου σε C ++, αφιερωμένη αντικείμενα πρέπει να αφαιρεθούν, πράγμα που θα αυξήσει περαιτέρω την διαχωρισμού (μέχρι 2580 MS).

1. Αντικείμενα πισίνας

Η προφανής λύση είναι να πάρει ένα μεγάλο μπλοκ μνήμης από το λειτουργικό σύστημα και γίνεται διαχωρισμός σε ίσα τμήματα του μεγέθους sizeof (Node), όταν κατανέμει τη μνήμη, να λάβει ένα μπλοκ από την πισίνα, όταν κυκλοφόρησε - επιστροφή στην πισίνα. Η πισίνα είναι πιο εύκολη στην οργάνωση με μια ενιαία λίστα (στοίβα).

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

Πρώτα απ 'όλα, θα χρειαστούμε μια πισίνα μεγάλων μπλοκ (σελίδες), οι οποίες απομακρύνονται από το OS ή το c-runtime. Μπορεί να οργανωθεί πάνω από τις λειτουργίες malloc και δωρεάν, αλλά για μεγαλύτερη απόδοση (για να παραλείψετε το επιπλέον επίπεδο αφαίρεσης), χρησιμοποιούμε VirtualAlloc / VirtualFree. Αυτές οι λειτουργίες διαθέτουν μπλοκ μνήμης, πολλαπλά 4K, καθώς και αποθεματικά το χώρο διεύθυνσης της διαδικασίας με μπλοκ, πολλαπλές 64k. Την ίδια στιγμή, καθορίζοντας τις επιλογές Δέσμευση και Reserve, θα πηδήξει ένα άλλο επίπεδο αφαίρεσης, διατηρώντας το χώρο διευθύνσεων και την προβολή των σελίδων μνήμης με ένα τηλεφώνημα.

Σελίδα

inline μέγεθος_t ευθυγράμμιση (size_t x, size_t a) (επιστροφή (((x-1) | (Α-1)) + 1;) // # ορισμός | (( (α) -1)) + 1) Πρότυπο Κατηγορία PagePool (Public: Κενό * GetPage () (void * Page \u003d VirtualAlloc (NULL, pagesize, MEM_COMMIT | MEM_RERSERVE, Page_ReadWrite)? Pages.push_back (Page)? Return Page?) ~ PagePool () (Για (Vector :: ITARATOR I \u003d PAGE.BEGIN (); I! \u003d PAGES.END (); ++ i) (VirtualFree (* I, 0, MEM_RELEASE))) Ιδιωτικό: Διάνυσμα σελίδες; )

Στη συνέχεια, οργανώστε την πισίνα των μπλοκ του καθορισμένου μεγέθους

Κλάση Blockpool

Πρότυπο. Class Blockpool: POGEPOOL (Δημόσια: blockpool (): Κεφάλι (NULL) (blocksize \u003d Στοίχιση (sizeof (T), Ευθυγράμμιση)? Μετράνε \u003d pagesize / blocksize?) Void * allocblock () (// TODO: LOCK (η) εάν (επικεφαλής) Formatnewpage (); κενό * tmp \u003d κεφάλι; κεφάλι \u003d * (void **) κεφάλι; επιστρέψτε το TMP;) κενό freeblock (κενό * tmp) (// todo: κλειδώματος (αυτό) * (void **) TMP \u003d HEAD; HEAD \u003d TMP;) Ιδιωτική: άκυρη * κεφαλή; size_t blocksize, μέγεθος_t, κενή μορφήNewpage () (κενός * tmp \u003d getpage ()? Κεφάλι \u003d tmp; για (μέγεθος_t i \u003d 0; i< count-1; i++) { void* next = (char*)tmp + BlockSize; *(void**)tmp = next; tmp = next; } *(void**)tmp = NULL; } };

Σχόλιο // ToDo: Κλείδωμα (αυτό) Οι θέσεις σημειώνονται, οι οποίοι απαιτούν το interpotional συγχρονισμό (για παράδειγμα, χρησιμοποιήστε εντελώςCriilySection ή Boost :: mutex).

Θα εξηγήσω γιατί όταν η "μορφοποίηση" η σελίδα δεν χρησιμοποιεί την αφαίρεση του ελεύθερουμπλεια για να προσθέσει ένα μπλοκ στην πισίνα. Αν κάτι σαν κάτι σαν γράφτηκε

Για (size_t i \u003d 0; i< PageSize; i += BlockSize) FreeBlock((char*)tmp+i);

Η σελίδα αυτή σχετικά με την αρχή του FIFO θα σημειωθεί "Αντίθετα":

Αρκετά μπλοκ που ζητήθηκαν από την πισίνα στη σειρά θα έχουν μειωμένη διεύθυνση. Και ο επεξεργαστής δεν θέλει να επιστρέψει, σπάει με το prefetch ( Απάντηση.: Δεν έχει σημασία για τους σύγχρονους επεξεργαστές). Εάν κάνετε σήμανση στον κύκλο
Για (size_t i \u003d pageize- (blocksize- (pageize% blocksize)), i! \u003d 0; i - \u003d blocksize) freeblock ...
Ο κύκλος σήμανσης θα συνεχίσει να αντιμετωπίζει προς τα πίσω.

Τώρα που παρασκευάζονται τα παρασκευάσματα, μπορείτε να περιγράψετε την τάξη βαθμού.
Πρότυπο. Class Blockalloc (κοινό: Στατικό κενό * Χειριστής νέους (size_t s) (αν (s! \u003d Μέγεθος (t)) (επιστροφή :: χειριστής Νέοι (s);) Επιστροφή Pool.allocblock ();) Static Void Operator Διαγραφή (κενός * M, size_t s) (εάν (s! \u003d Μέγεθος (t)) (:: χειριστής διαγραφή (m);) αλλιώς εάν (m! \u003d Null) (pool.freeblock (m);) // todo: Εφαρμογή Nothrow_t Υπερφόρτωση, σύμφωνα με Borisko «ΣΧΟΛΙΟ // http://habrahabr.ru/post/148657/#comment_5020297 // Αποφύγετε την απόκρυψη τοποθέτηση της Νέας Αυτό το» S που χρειάζεται ο Stl Εμπορευματοκιβώτια ... στατική άκυρη * Χειριστής Νέα (size_t, Void * M) (επιστροφή m;) // ... και η προειδοποίηση σχετικά με την τοποθέτηση τοποθέτησης διαγράψει ... Στατικός κενός χειριστής διαγραφή (κενή *, κενή *) () Ιδιωτική: Στατική Blockpool Πισίνα; ) Πρότυπο. Μπλοκ Blockalloc. :: Πισίνα;

Θα εξηγήσω γιατί χρειάζεστε ελέγχους Εάν (s! \u003d μέγεθος (t))
Πότε δουλεύουν; Στη συνέχεια, όταν η τάξη δημιουργείται / διαγραφεί από τη βάση T.
Οι κληρονόμοι θα χρησιμοποιήσουν το συνηθισμένο νέο / διαγραφές, αλλά μπορείτε επίσης να αναμίξετε το Blockalloc. Έτσι, είμαστε εύκολα και με ασφάλεια που οι τάξεις θα πρέπει να χρησιμοποιούν τις πισίνες, χωρίς να φοβόμαστε να σπάσουν κάτι στο πρόγραμμα. Η πολλαπλή κληρονομιά λειτουργεί επίσης μεγάλη με αυτό το μίγμα.

Ετοιμος. Να διαδραματίσει κόμβο από το blockalloc και να επαναφέρετε μια δοκιμή.
Ο χρόνος δοκιμής είναι τώρα - 120 ms. 5 φορές ταχύτερα. Αλλά στο C # Allocator είναι ακόμα καλύτερο. Πιθανώς, δεν υπάρχει μόνο μια συνδεδεμένη λίστα. (Εάν αμέσως μετά τη νέα, καλέστε αμέσως τη διαγραφή και, συνεπώς, δεν ξοδεύετε πολλή μνήμη, γνωρίζοντας τα δεδομένα στην προσωρινή μνήμη, παίρνουμε 62 ms. Παράξενο. Ακριβώς όπως ένα U.NET CLR, σαν να επιστρέφει αμέσως τις απελευθερωμένες τοπικές μεταβλητές στη σχετική πισίνα, χωρίς να περιμένει την GC)

2. Δοχείο και περιεχόμενο πετρελαίου του

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

Για παράδειγμα, μπορεί να είναι μια κλάση xmldocument γεμάτη με κλάσεις κόμβων και χαρακτηριστικών, καθώς και c-χορδές (char *) που λαμβάνονται από το κείμενο μέσα στον κόμβο. Ή μια λίστα αρχείων και καταλόγων στον διαχειριστή αρχείων, κατεβάστηκε μία φορά όταν ξαναδιαβάσετε τον κατάλογο και δεν αλλάζουν πλέον.

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

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

Τάξη pointerbumpallocator

Πρότυπο. Class PointerBumplolocator (κοινό: PointerbumpaLocator (): Δωρεάν (0) () Void * ALLOCBLOCK (SIZE_T BLOCK) (// TODO: Κλείδωμα (αυτό) Block \u003d Ευθυγράμμιση (μπλοκ, ευθυγράμμιση); εάν (block\u003e δωρεάν) (δωρεάν \u003d ευθυγράμμιση (μπλοκ, σελίδες). κεφάλι \u003d getpage (δωρεάν)?) void * tmp \u003d κεφάλι; κεφάλι \u003d κεφάλι \u003d (char *) κεφάλι + μπλοκ; δωρεάν - \u003d μπλοκ; επιστροφή tmp;) ~ pointerbumpallocator () (για (διάνυσμα) :: ITARATOR I \u003d PAGE.BEGIN (); I! \u003d PAGES.END (); ++ i) (VirtualFree (* i, 0, Mem_release)?)) Ιδιωτικά: void * getpage (μέγεθος size_t) (void * Page \u003d VirtualAlloc (NULL, ΜΕΓΕΘΟΣ, MEM_COMMIT | MEM_RERVE, Page_ReadWrite)? Pages.push_back (Σελίδα) · Επιστροφή σελίδας;) διάνυσμα Σελίδες; Άκυρη * κεφάλι? Size_t δωρεάν; ) Typedef PointerBumpaLocator<> Defaultacator;

Τέλος, περιγράφουμε το πρόσμιγμα παιδικής βοήθειας με την υπερφορτωμένη νέα και διαγραφή, απευθυνόμενη στον καθορισμένο κονδύλιο:

Πρότυπο. STRUCT CHILDOBJECT (Return Allocator.AllocBlock (S)?) Στατική άκυρη * Χειριστής Νέα (Return Allocator-\u003e Allocblock (S)?) Στατική κενό φορέα διαγράψετε (void *, size_t) () // * 1 στατική άκυρη χειριστή delete (void *, a *) () στατική άκυρη χειριστή διαγράψετε (void *, α &) () Ιδιωτική: στατική άκυρη * χειριστής Νέα (size_t S)?)?

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

Νέες (... Παράμετροι για τον χειριστή ...) Childobject (... Παράμετροι σχεδιαστών ...)

Για ευκολία, θέλω δύο νέους χειριστές που έλαβαν A & A *.
Εάν ο αλλοδαπός προστίθεται στη μητρική τάξη ως μέλος, πιο βολικό για την πρώτη επιλογή:
Κόμβος \u003d Νέο (ALOCATOR) XMLNODE (NODENAME).
Εάν ο αλλοδαπός προστίθεται ως πρόγονος (ανάμιξη), είναι πιο βολικό για το δεύτερο:
Κόμβος \u003d Νέο (αυτό) XMLNODE (NoDename);

Μια ειδική σύνταξη δεν παρέχεται για την κλήση διαγραφής, ο μεταγλωττιστής θα προκαλέσει μια τυπική διαγραφή (σημειωμένη * 1), ανεξάρτητα από ποιες από τις νέες δηλώσεις χρησιμοποιήθηκαν για τη δημιουργία ενός αντικειμένου. Δηλαδή, η σύνταξη διαγραφή είναι φυσιολογική:
ΔΙΑΓΡΑΦΗ ΚΟΜΒΟΥ;

Αν υπάρχει μια εξαίρεση στον σχεδιαστή childobject (ή τον κληρονόμο του), ένας διαγραφής καλείται με μια υπογραφή που αντιστοιχεί στην υπογραφή του νέου φορέα που χρησιμοποιείται κατά τη δημιουργία αυτού του αντικειμένου (η πρώτη παράμετρος size_t θα αντικατασταθεί από Void *).

Η τοποθέτηση του νέου χειριστή στο ιδιωτικό τμήμα προστατεύει από τη νέα παράμετρο Allest.

Θα δώσω ένα πλήρες παράδειγμα χρήσης του ζεύγους κατανομής-παιδικής ηλικίας:

Παράδειγμα

class Xmldocument: Δημόσιος προεπιλογής (κοινό: ~ Xmldocument () (για (διάνυσμα :: ITARTATOR I \u003d NODES.BEGIN (); I! \u003d Nodes.end (); ++ i) (Delete (* i)?)) Void addnode (char * ΠΕΡΙΕΧΟΜΕΝΟΥ, ΧΑΡ * NAME) (char * c \u003d (char *) ALLOCBLOCK (strlen (ΠΕΡΙΕΧΟΜΕΝΟ) +1)? Strcpy (C, ΠΕΡΙΕΧΟΜΕΝΟ)? char * n \u003d (char *) allocblock (strlen (όνομα) +1); strcpy (n, περιεχόμενο); nodes.push_back (νέο XMLNODE (C, N));) Κλάση XMLNODE: Δημόσιος παιδικός σκοπός (Δημόσια: XMLNODE (Char * _CONTENT, CHAR * _NAME): Περιεχόμενο (_CONTENT), όνομα (_Name) () Ιδιωτικό: char * περιεχόμενο; char * όνομα;); Ιδιωτικός: διάνυσμα κόμβοι; )

Συμπέρασμα. Το άρθρο γράφτηκε πριν από 1,5 χρόνια για το Sandbox, αλλά δυστυχώς, δεν του άρεσε ο συντονιστής.

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

Υπάρχουν δύο τύποι στατικών μεταβλητών:

  • Παγκόσμιες μεταβλητές - Αυτές είναι οι μεταβλητές που ορίζονται Εξωτερικές λειτουργίες, στην περιγραφή της οποίας δεν υπάρχει στατική λέξη. Συνήθως περιγραφές Οι παγκόσμιες μεταβλητές που περιλαμβάνουν τη λέξη εξωτερική μεταφέρονται σε αρχεία κεφαλίδας (αρχεία H). Η λέξη εξωτερική σημαίνει ότι η μεταβλητή περιγράφεται, αλλά δεν δημιουργείται σε αυτό το σημείο του προγράμματος. Ορισμοί Παγκόσμιες μεταβλητές, δηλ. Οι περιγραφές χωρίς εξωτερική λέξη τοποθετούνται στα αρχεία εφαρμογής (αρχεία C ή αρχεία CPP). Παράδειγμα: Η παγκόσμια μεταβλητή Maxind περιγράφεται δύο φορές:
    • Σε ένα αρχείο H χρησιμοποιώντας μια συμβολοσειρά

      Εξωτερική Int Maxind.

      Αυτή η περιγραφή αναφέρει την παρουσία μιας τέτοιας μεταβλητής, αλλά δεν δημιουργεί αυτή τη μεταβλητή!
    • Σε ένα αρχείο CPP χρησιμοποιώντας μια συμβολοσειρά

      int maxind \u003d 1000;

      Αυτή είναι μια περιγραφή Δημιουργεί Η μεταβλητή Maxind και το εκχωρεί την αρχική τιμή του 1000. Σημειώστε ότι το πρότυπο γλώσσας δεν απαιτεί υποχρεωτική ανάθεση αρχικών τιμών με παγκόσμιες μεταβλητές, αλλά, ωστόσο, είναι πάντα καλύτερο να το κάνετε, διαφορετικά η μεταβλητή θα περιέχει μια απρόβλεπτη αξία (σκουπίδια, όπως λένε οι προγραμματιστές). Η αρχικοποίηση όλων των παγκόσμιων μεταβλητών στον ορισμό τους είναι ένας καλός κανόνας στυλ.
    Οι παγκόσμιες μεταβλητές ονομάζονται έτσι επειδή είναι διαθέσιμες σε οποιοδήποτε σημείο του προγράμματος σε όλα τα αρχεία του. Ως εκ τούτου, τα ονόματα των παγκόσμιων μεταβλητών πρέπει να είναι αρκετά μακρά ώστε να αποφεύγουν την τυχαία σύμπτωση των ονομάτων δύο διαφορετικών μεταβλητών. Για παράδειγμα, τα ονόματα x ή n για την παγκόσμια μεταβλητή δεν είναι κατάλληλα.
  • Στατικές μεταβλητές - Αυτές είναι μεταβλητές, στην περιγραφή της οποίας υπάρχει η λέξη στατική. Κατά κανόνα, περιγράφονται στατικές μεταβλητές Εξωτερικές λειτουργίες. Τέτοιες στατικές μεταβλητές είναι παρόμοιες με την παγκόσμια, με μία εξαίρεση: το πεδίο της στατικής μεταβλητής περιορίζεται από ένα αρχείο, μέσα στο οποίο ορίζεται - και, επιπλέον, μπορεί να χρησιμοποιηθεί μόνο μετά την περιγραφή του, δηλ. Παρακάτω στο κείμενο. Για το λόγο αυτό, η περιγραφή των στατικών μεταβλητών συνήθως λαμβάνεται στην αρχή του αρχείου. Σε αντίθεση με τις παγκόσμιες μεταβλητές, στατικές μεταβλητές ποτέ Δεν περιγράφεται σε αρχεία H (εξωτερικά και στατικά τροποποιητικά συγκρούονται μεταξύ τους). Συμβουλή: Χρησιμοποιήστε στατικές μεταβλητές εάν πρέπει να είστε διαθέσιμοι μόνο για τις λειτουργίες που περιγράφονται στο εσωτερικό το ίδιο αρχείο. Εάν είναι δυνατόν, μην χρησιμοποιείτε παγκόσμιες μεταβλητές σε τέτοιες καταστάσεις, αυτό θα αποφύγει τις συγκρούσεις ονόματος κατά την εφαρμογή μεγάλων έργων που αποτελούνται από εκατοντάδες αρχεία.
    • Μια στατική μεταβλητή μπορεί να περιγραφεί μέσα στη λειτουργία, αν και συνήθως κανείς δεν το κάνει. Η μεταβλητή δεν τοποθετείται στη στοίβα, αλλά σε στατική μνήμη, δηλ. Δεν μπορεί να χρησιμοποιηθεί κατά τη διάρκεια της επανάληψης και η τιμή του αποθηκεύεται μεταξύ διαφορετικών εισροών στη λειτουργία. Το πεδίο αυτής μιας μεταβλητής περιορίζεται από το σώμα της λειτουργίας στην οποία ορίζεται. Διαφορετικά, είναι παρόμοιο με μια στατική ή παγκόσμια μεταβλητή. Σημειώστε ότι η στατική λέξη-κλειδί στη γλώσσα του C χρησιμοποιείται για δύο διαφορετικούς σκοπούς:
      • Ως ένδειξη του τύπου μνήμης: η μεταβλητή βρίσκεται σε στατική μνήμη και όχι στη στοίβα.
      • Ως τρόπο περιορισμού του πεδίου της ορατότητας από ένα μεταβλητό πλαίσιο ενός αρχείου (στην περίπτωση περιγραφής της μεταβλητής εκτός της λειτουργίας).
  • Η λέξη στατική μπορεί να υπάρχει στην κεφαλίδα λειτουργίας. Στην περίπτωση αυτή, χρησιμοποιείται μόνο για να περιοριστεί το πεδίο εφαρμογής του ονόματος της λειτουργίας από το πλαίσιο ενός αρχείου. Παράδειγμα:

    Στατική int gcd (int x, int y); // Πρωτότυπο F-│. . . Στατική Int GCD (Int x, int y) (// Πωλήσεις ....)

    Συμβουλή: Χρησιμοποιήστε τον στατικό τροποποιητή στην κεφαλίδα λειτουργίας, εάν είναι γνωστό ότι η λειτουργία θα καλείται μόνο μέσα σε ένα αρχείο. Η στατική λέξη πρέπει να υπάρχει τόσο στην πρωτότυπη περιγραφή της λειτουργίας όσο και στην κεφαλίδα της λειτουργίας όταν εφαρμόζεται.

Βιτρώ ή τοπική μνήμη

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

Οι τοπικές μεταβλητές μπορούν να χρησιμοποιηθούν κατά τη διάρκεια της επανάληψης, διότι όταν επαναλαμβάνεται η είσοδος, δημιουργείται ένα νέο σύνολο τοπικών μεταβλητών στη λειτουργία στη στοίβα και το προηγούμενο σετ δεν καταστρέφεται. Για τον ίδιο λόγο, οι τοπικές μεταβλητές είναι ασφαλείς όταν χρησιμοποιούν θέματα σε παράλληλο προγραμματισμό (βλ. Ενότητα 2.6.2). Οι προγραμματιστές καλούν μια τέτοια λειτουργία λειτουργίας Επανεπεξεργασίααπό τα Αγγλικά Επαναλάβετε τη δυνατότητα - την ικανότητα επανεισόδου. Αυτή είναι η πολύ σημαντική ποιότητα από την άποψη της αξιοπιστίας και της ασφάλειας του προγράμματος! Το πρόγραμμα που εργάζεται με στατικές μεταβλητές δεν διαθέτει αυτή την ιδιότητα, οπότε πρέπει να χρησιμοποιηθεί για την προστασία των στατικών μεταβλητών Μηχανισμοί συγχρονισμού (Βλέπε 2.6.2) και η λογική του προγράμματος είναι πολύ περίπλοκη. Θα πρέπει πάντα να αποφύγετε τη χρήση παγκόσμιων και στατικών μεταβλητών εάν μπορείτε να κάνετε τοπικά.

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

Δυναμική μνήμη ή μια δέσμη

Εκτός από τη στατική και στοίβα μνήμη, εξακολουθεί να υπάρχει σχεδόν απεριόριστος πόρος μνήμης, ο οποίος ονομάζεται Δυναμικός, ή Χολή (ΣΩΡΟΣ). Το πρόγραμμα μπορεί να καταγράψει τις δυναμικές περιοχές μνήμης του επιθυμητού μεγέθους. Μετά τη χρήση, πρέπει να απελευθερωθεί η δυναμική περιοχή μνήμης που έχει ληφθεί προηγουμένως.

Κάτω από τη δυναμική μνήμη, δίνεται ο χώρος της εικονικής μνήμης της διαδικασίας μεταξύ στατικής μνήμης και στοίβας. (Ο μηχανισμός εικονικής μνήμης εξετάστηκε στο τμήμα 2.6.) Συνήθως η στοίβα βρίσκεται σε ανώτερες εικονικές διευθύνσεις μνήμης και αναπτύσσεται προς μια μείωση των διευθύνσεων (βλ. Παράγραφο 2.3). Το πρόγραμμα και τα σταθερά δεδομένα τοποθετούνται σε διευθύνσεις Junior, οι στατικές μεταβλητές είναι πάνω. Ο χώρος πάνω από τις στατικές μεταβλητές και κάτω από τη στοίβα παίρνει δυναμική μνήμη:

διεύθυνση Περιεχόμενα μνήμης

Κωδικός προγράμματος και δεδομένα

Προστατεύονται από την αλλαγή

...

Στατικές μεταβλητές

προγράμματα

Δυναμική μνήμη

Μέγιστη. Διεύθυνση (2 32 -4)

σωρός

Η δυναμική δομή μνήμης υποστηρίζεται αυτόματα από το σύστημα εκτέλεσης SI ή C ++. Η δυναμική μνήμη αποτελείται από αιχμαλωτισμένα και ελεύθερα τμήματα, καθένα από τα οποία προηγείται από τον περιγραφέα του τμήματος. Κατά την εκτέλεση αίτησης για να καταγράψετε τη μνήμη, το σύστημα εκτέλεσης αναζητά ένα ελεύθερο τμήμα επαρκούς μεγέθους και καταγράφει το τμήμα του απαιτούμενου μήκους σε αυτό. Όταν το τμήμα μνήμης απελευθερωθεί, χαρακτηρίζεται ως δωρεάν, εάν είναι απαραίτητο, μερικές συμβάσεις των προσεχείρων ελεύθερων τμημάτων συνδυάζονται.

Το τυποποιημένο malloc και οι ελεύθερες λειτουργίες χρησιμοποιούνται για τη σύλληψη και απελευθέρωση της δυναμικής μνήμης, οι περιγραφές των πρωτοτύπων τους περιέχονται στο πρότυπο αρχείο κεφαλίδας "stdlib.h". (Το όνομα Malloc είναι μια μείωση από Τη διάθεση της μνήμης. - "Σύλληψη μνήμης".) Τα πρωτότυπα αυτών των λειτουργιών μοιάζουν με αυτό:

Άκυρη * malloc (size_t n); // Στείλτε τη θέση μνήμης // το μέγεθος της N Byte κενό Δωρεάν (κενό * P); // Απελευθερώστε ένα οικόπεδο // μνήμη με τη διεύθυνση P

Εδώ n είναι το μέγεθος της αιχμαλωτισμένης περιοχής σε bytes, το μέγεθος_t είναι το όνομα ενός από τους ακέραιους τύπους που καθορίζουν το μέγιστο μέγεθος της αιχμαλωτισμένης περιοχής. Ο τύπος μεγέθους_T ρυθμίζεται στο πρότυπο αρχείο κεφαλίδας stdlib.h χρησιμοποιώντας τον χειριστή του Typedef (βλ. C. 117). Αυτό εξασφαλίζει την ανεξαρτησία του κειμένου του προγράμματος SI από την αρχιτεκτονική που χρησιμοποιείται. Σε μια αρχιτεκτονική 32-bit, ο τύπος μεγέθους_T ορίζεται ως ένας μη υπογεγραμμένος ακέραιος:

typedef μη υπογεγραμμένο int size_t;

Η λειτουργία Malloc επιστρέφει τη διεύθυνση της αιχμαλωτισμένης περιοχής μνήμης ή μηδέν σε περίπτωση βλάβης (όταν δεν υπάρχει δωρεάν οικόπεδο επαρκώς μεγάλου μεγέθους). Η ελεύθερη λειτουργία απελευθερώνει την περιοχή μνήμης με μια καθορισμένη διεύθυνση. Για να καθορίσετε τη διεύθυνση, χρησιμοποιείται ένας κοινόχος κενός * δείκτης. Αφού καλέσετε τη λειτουργία Malloc, είναι απαραίτητο να οδηγήσετε σε ένα δείκτη σε έναν συγκεκριμένο τύπο χρησιμοποιώντας τον τύπο τύπου τύπου, βλέπε κεφάλαιο 3.4.11. Για παράδειγμα, το ακόλουθο παράδειγμα καταγράφει μια δυναμική περιοχή μνήμης με μέγεθος 4000 bytes, η διεύθυνσή του αντιστοιχεί σε δείκτη σε μια σειρά 1000 ακεραίων:

int * a; // δείκτης σε μια σειρά ακεραίων. . . a \u003d (int *) malloc (1000 * μέγεθος (int)).

Η έκφραση στο επιχείρημα της λειτουργίας Malloc είναι 4000, δεδομένου ότι το μέγεθος ενός ακέραιου αριθμού μεγέθους (int) είναι ίσο με τέσσερα bytes. Για να μετατρέψετε έναν δείκτη, χρησιμοποιήστε τον τύπο τύπου (int *) από τον γενικευμένο δείκτη τύπου στον δείκτη ακέραιου.

Παράδειγμα: Εκτύπωση N πρώτους απλούς αριθμούς

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

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

#Περιλαμβάνω. #Περιλαμβάνω. #Περιλαμβάνω. int main () (int n; // Απαιτούμενος αριθμός απλών αριθμών int k; // Τρέχων αριθμός πρωταρχικών αριθμών που βρέθηκαν int * a; // δείκτη σε μια σειρά από το πιο απλό int p, // άλλου αριθμού int r. // Σύνολο μέρος Τετραγωνική ρίζα από P int i? // απλό ενός απλού δείκτη διαιρέτη BOOL Prime? // Σημείο των απλότητας printf ( «Εισάγετε τον αριθμό των απλών:»)? scanf ( «% d», & n)? αν (n<= 0) // Некорректное значение => Επιστρέψτε 1; // Συμπληρώστε τον κωδικό σφάλματος // Καταγράψτε τη μνήμη για μια σειρά πρωταρχικών αριθμών a \u003d (int *) malloc (n * sizeof (int)). a \u003d 2; k \u003d 1; // Προσθέστε δύο στη συστοιχία εκτύπωσης ("% D", A); // και εκτυπώστε το P \u003d 3; Ενώ (Κ.< n) { // Проверяем число p на простоту r = (int)(// Целая часть корня sqrt((double) p) + 0.001); i = 0; prime = true; while (i < k && a[i] <= r) { if (p % a[i] == 0) { // p делится на a[i] prime = false; // => P δεν είναι εύκολο, σπάσιμο? // βγαίνουν από τον κύκλο) ++ i; // Για την επόμενη απλό διαχωριστικό) εάν (Prime) (// Αν βρεθεί ένα απλό αριθμό, A [k] \u003d p? // στη συνέχεια προσθέστε το σε μια σειρά ++ k? // Αύξηση του αριθμού των απλών printf ( "% d", ρ)? // Εκτύπωση ο απλός αριθμός εάν (k% 5 \u003d\u003d 0) (// μετάβαση σε μια νέα γραμμή printf ( "\\ n")? // μετά από κάθε πέντε αριθμούς)) P + \u003d 2; // στον επόμενο περίεργο αριθμό) εάν (k% 5! \u003d 0) (printf ("\\ n"); // μεταφράστε μια συμβολοσειρά) // Απελευθερώστε τη δυναμική μνήμη δωρεάν (α); Επιστροφή 0; )

Ένα παράδειγμα αυτού του προγράμματος:

Εισάγετε τον αριθμό των απλών: 50 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229

Οι χειριστές νέες και διαγράψουν τη γλώσσα C ++

Στη γλώσσα C ++, οι νέοι και οι διαγραμμένοι χειριστές χρησιμοποιούνται για τη σύλληψη και την απελευθέρωση της δυναμικής μνήμης. Είναι μέρος της γλώσσας C ++, σε αντίθεση με το Malloc και τις ελεύθερες λειτουργίες, οι οποίες περιλαμβάνονται στη βιβλιοθήκη τυποποιημένων λειτουργιών SI.

Ας t να είναι κάποιος τύπος γλώσσας Si ή C ++, το P είναι ένας δείκτης στο αντικείμενο τύπου t. Στη συνέχεια, για να τραβήξετε μια μνήμη σε μέγεθος σε ένα στοιχείο τύπου t, ο νέος χειριστής χρησιμοποιείται:

T * p; p \u003d νέο t;

Για παράδειγμα, χρησιμοποιείται ένα θραύσμα για τη λήψη οκτώ byte στον πραγματικό αριθμό διπλού τύπου

Διπλό * P; P \u003d νέο διπλό?

Όταν χρησιμοποιείτε νέα, σε αντίθεση με το malloc, δεν χρειάζεται να δώσετε δείκτη από τον τύπο κενό * στον επιθυμητό τύπο: Ο νέος χειριστής επιστρέφει έναν δείκτη στον τύπο που καταγράφεται μετά τη λέξη Νέα. Συγκρίνετε δύο ισοδύναμα θραύσματα στο C και C ++.

Το C ++ υποστηρίζει τρεις κύριους τύπους απαλλάσσω (ή περισσότερο "Κατανομή") Μνήμη, με δύο από τα οποία, είμαστε ήδη εξοικειωμένοι:

Στατική κατανομή μνήμης Και για τις δύο μεταβλητές. Η μνήμη κατανέμεται μία φορά όταν ξεκινήσει το πρόγραμμα και αποθηκεύεται σε όλο το πρόγραμμα.

Αυτόματη κατανομή μνήμης Και. Η μνήμη κατανέμεται κατά την είσοδο στο μπλοκ στο οποίο βρίσκονται αυτές οι μεταβλητές και διαγράφονται όταν βγαίνει.

Δυναμική κατανομή μνήμης είναι το θέμα αυτού του μαθήματος.

Δυναμική επιλογή μεταβλητών

Τόσο η στατική όσο και η αυτόματη κατανομή μνήμης έχει δύο κοινές ιδιότητες:

Πώς λειτουργεί η δυναμική κατανομή της μνήμης;

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

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

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

Απελευθέρωση μνήμης

Όταν αποθέσετε δυναμικά μια μεταβλητή, μπορείτε επίσης να την αρχικοποιήσετε μέσω ή ομοιόμορφης αρχικοποίησης (στο C ++ 11):

int * pTR1 \u003d Νέα INT (7). // χρησιμοποιώντας άμεση αρχικοποίηση Int * PTR2 \u003d Νέα INT (8); // Χρήση ομοιόμορφης αρχικοποίησης

Όταν όλα όσα χρειάζεστε γίνονται με μια δυναμικά επιλεγμένη μεταβλητή - πρέπει να καθορίσετε ρητά το C ++ για να απελευθερώσετε αυτή τη μνήμη. Για μεταβλητές, αυτό γίνεται χρησιμοποιώντας Χειριστής διαγράφω.:

// Ας υποθέσουμε ότι το PTR έχει ήδη επισημανθεί χρησιμοποιώντας το νέο διαγραφέα PTR PTRA εκμετάλλευσης. // Επιστρέψτε τη μνήμη για να υποδείξετε το PTR, πίσω στο λειτουργικό σύστημα PTR \u003d 0; // Κάνετε PTR από μηδενικό δείκτη (χρησιμοποιήστε το Nullptr αντί για 0 \u200b\u200bστο C ++ 11)

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

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

Σημείωση, διαγράψτε έναν δείκτη που δεν υποδεικνύει δυναμικά την κατανομή της μνήμης μπορεί να προκαλέσει προβλήματα.

Κρεμαστά σημάδια

Το C ++ δεν παρέχει εγγυήσεις σχετικά με το τι θα συμβεί με το περιεχόμενο της απελευθερωμένης μνήμης ή με την τιμή του απομακρυσμένου δείκτη. Στις περισσότερες περιπτώσεις, η μνήμη που επιστρέφεται από το λειτουργικό σύστημα θα περιέχει τις ίδιες τιμές που ΑπελευθέρωσηΚαι ο δείκτης θα παραμείνει δείχνει μόνο ήδη απελευθερωμένη (απομακρυσμένη) μνήμη.

Ο δείκτης που δείχνει απελευθερωμένη μνήμη ονομάζεται Κρεμαστό δείκτη. Η RAMING ή η διαγραφή ενός κρεμασμένου δείκτη θα οδηγήσει σε απροσδόκητα αποτελέσματα. Εξετάστε το ακόλουθο πρόγραμμα:

#Περιλαμβάνω. int main () (int * ptr \u003d new int, * ptr \u003d 8, // βάζουμε την τιμή στο διαγραμμένο διαγραμμένο κύτταρο μνήμης ptr, // επιστρέψει τη μνήμη πίσω στο λειτουργικό σύστημα. Το PTR κρέμεται τώρα STD :: Cout<< *ptr; // разыменование висячего указателя приведёт к неожиданным результатам delete ptr; // попытка освободить память снова приведёт к неожиданным результатам также return 0; }

#Περιλαμβάνω.

iNT κύριο ()

int * ptr \u003d new int; // Διαθέστε δυναμικά μια ακέραια μεταβλητή

* PTR \u003d 8; // τοποθετήστε την τιμή στο επιλεγμένο κύτταρο μνήμης

Διαγράψτε το PTR; // επιστρέψτε τη μνήμη πίσω στο λειτουργικό σύστημα. Το PTR είναι τώρα ένας κρεμαστός δείκτης

sTD :: Cout.<< * ptr ; // raming Το κρεμαστό δείκτη θα οδηγήσει σε απροσδόκητα αποτελέσματα

Διαγράψτε το PTR; // Η προσπάθεια να ελευθερώσει μνήμη θα οδηγήσει σε απροσδόκητα αποτελέσματα επίσης

Επιστροφή 0;

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

Η διαδικασία απελευθέρωσης μνήμης μπορεί επίσης να οδηγήσει στη δημιουργία αρκετά Κρεμαστά δείκτες. Εξετάστε το ακόλουθο παράδειγμα:

#Περιλαμβάνω. Int main () (int * ptr \u003d new int; // δυναμικά επιλέξτε την μεταβλητή Int * otherptr \u003d ptr \u003d ptr; // lothptr, τώρα υποδεικνύει την ίδια συμμαχική μνήμη με το PTR διαγράψτε το PTR. και το άλλοPTR NOW PTR \u003d 0; // PTR είναι τώρα Nullptr // Ωστόσο, το OtherPTR εξακολουθεί να είναι ένας κρεμαστός δείκτης! Επιστροφή 0;)

#Περιλαμβάνω.

iNT κύριο ()

int * ptr \u003d new int; // Διαθέστε δυναμικά μια ακέραια μεταβλητή

int * otherptr \u003d ptr; // otherptr τώρα υποδεικνύει την ίδια κατανομή μνήμης ως PTR

Διαγράψτε το PTR; // επιστρέψτε τη μνήμη πίσω στο λειτουργικό σύστημα. Ptr και otherptr τώρα κρέμονται σημάδια

pTR \u003d 0; // PTR NOW NULLPTR

// Ωστόσο, το άλλοPTR εξακολουθεί να είναι ένας κρεμαστός δείκτης!

Επιστροφή 0;

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

Δεύτερον, όταν διαγράψετε έναν δείκτη και αν δεν φύγει αμέσως μετά την αφαίρεση, πρέπει να γίνει μηδέν, δηλ. Αντιστοιχίστε μια τιμή 0 (ή σε C ++ 11). Κάτω από την "παραγωγή από το πεδίο εφαρμογής αμέσως μετά τη διαγραφή" σημαίνει ότι διαγράφετε τον δείκτη στο άκρο του μπλοκ στο οποίο δηλώνεται.

Κανόνας: Αντιστοιχίστε μια τιμή 0 (ή Nullptr στο C ++ 11) σε απομακρυσμένους πινακίδες εάν δεν αφήνουν το πεδίο αμέσως μετά την αφαίρεση.

Χειριστή νέα.

Όταν ζητάτε μνήμη από το λειτουργικό σύστημα, σε σπάνιες περιπτώσεις, ενδέχεται να μην είναι διαθέσιμο (δηλ., Δεν μπορεί να είναι διαθέσιμο).

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

Σε πολλές περιπτώσεις, η διαδικασία δημιουργίας εξαίρεσης από τον νέο φορέα εκμετάλλευσης (καθώς και η αποτυχία του προγράμματος) είναι ανεπιθύμητη, επομένως υπάρχει μια εναλλακτική μορφή του νέου χειριστή που επιστρέφει έναν μηδενικό δείκτη εάν η μνήμη δεν μπορεί να κατανεμηθεί. Απλά πρέπει να προσθέσετε std :: nothrow σταθερή Μεταξύ νέου και τύπου δεδομένων λέξεων-κλειδιών:

int * value \u003d new (std :: nothrow) int; // ο δείκτης τιμής θα είναι μηδέν εάν η δυναμική επιλογή μιας μεταβλητής ακέραιων μεταβλητών δεν θα εκτελεστεί

Στο παραπάνω παράδειγμα, εάν το νέο δεν επιστρέψει τον δείκτη με δυναμικά αποκλειστική μνήμη, ο μηδενικός δείκτης θα επιστραφεί.

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

int * value \u003d new (std :: nothrow) int; // Αίτηση για δυναμική μνήμη για ακέραια τιμή εάν (! Αξία) // επεξεργασία της θήκης όταν η νέα επιστροφή NULL (δηλ. Μνήμη δεν απελευθερώνεται) (// Επεξεργασία αυτής της θήκης STD :: Cout<< "Could not allocate memory"; }

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

Μηδενικές δείκτες και δυναμική κατανομή μνήμης

Οι μηδενικοί δείκτες (δείκτες με τιμή 0 ή nullptr) είναι ιδιαίτερα χρήσιμες στη διαδικασία της δυναμικής κατανομής μνήμης. Η παρουσία τους όπως θα μας αναφερθεί: "Αυτός ο δείκτης δεν διαθέτει μνήμη." Και αυτό, με τη σειρά του, μπορεί να χρησιμοποιηθεί για την εκτέλεση της κατανομής μνήμης υπό όρους:

// Εάν η PTR-Y δεν έχει ακόμη κατανεμηθεί μνήμη, τότε το διαθέτουμε εάν (! PTR) PTR \u003d NEW INT;

Η αφαίρεση του μηδενικού δείκτη δεν επηρεάζει τίποτα. Έτσι, τα ακόλουθα δεν είναι απαραίτητα:

Εάν (PTR) διαγράψτε το PTR.

Εάν (PTR)

Διαγράψτε το PTR;

Αντ 'αυτού, μπορείτε απλά να γράψετε:

Διαγράψτε το PTR;

Εάν το PTR δεν είναι μηδέν, τότε η δυναμικά αφιερωμένη μεταβλητή θα διαγραφεί. Εάν η τιμή του δείκτη είναι μηδενική, τότε δεν συμβαίνει τίποτα.

Ελλειψη μνήμης

Η δυναμικά επιλεγμένη μνήμη δεν έχει πεδίο εφαρμογής, δηλ. Παραμένει επισημασμένο μέχρι να κυκλοφορήσει σαφώς ή έως ότου το πρόγραμμά σας θα ολοκληρώσει την εκτέλεση του (και το λειτουργικό σύστημα θα καθαρίσει όλα τα buffer μνήμης μόνοι σας). Ωστόσο, οι δείκτες που χρησιμοποιούνται για την αποθήκευση δυναμικά επιλεγμένων διευθύνσεων μνήμης ακολουθούν τους κανόνες της ορατότητας των συνήθων μεταβλητών. Αυτή η ασυνέπεια μπορεί να προκαλέσει ενδιαφέρουσα συμπεριφορά. Για παράδειγμα:

κενό dosomething () (int * ptr \u003d new int;)

Η δυναμική κατανομή μνήμης είναι απαραίτητη για την αποτελεσματική χρήση της μνήμης του υπολογιστή. Για παράδειγμα, γράψαμε κάποιο είδος προγράμματος που επεξεργάζεται μια συστοιχία. Κατά τη σύνταξη αυτού του προγράμματος, ήταν απαραίτητο να δηλώσετε μια συστοιχία, δηλαδή, για να το ορίσετε ένα σταθερό μέγεθος (για παράδειγμα, από 0 έως 100 στοιχεία). Στη συνέχεια, αυτό το πρόγραμμα δεν θα είναι καθολικό, επειδή μπορεί να επεξεργαστεί μια σειρά από όχι περισσότερα από 100 στοιχεία. Και αν χρειαζόμαστε μόνο 20 στοιχεία, αλλά στη μνήμη υπάρχει μια θέση κάτω από 100 στοιχεία, επειδή η δήλωση του πίνακα ήταν στατική και η χρήση της μνήμης είναι εξαιρετικά αποτελεσματική.

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

// Παράδειγμα χρήσης της λειτουργίας του νέου int * ptrvalue \u003d new int; // όπου ptrvalue είναι ένας δείκτης στο ειδικό τμήμα μνήμης του τύπου Int // New - η λειτουργία της επισήμανσης της ελεύθερης μνήμης για το δημιουργημένο αντικείμενο.

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

// Παράδειγμα χρήσης της διαγραφής: Διαγράψτε τη λειτουργία Ptrvalue; // όπου το Ptrvalue είναι ένας δείκτης στο ειδικό τμήμα μνήμης του τύπου INT // Διαγραφή - Λειτουργία απελευθέρωσης μνήμης

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

// new_delete.cpp: Καθορίζει το σημείο εισόδου για την εφαρμογή κονσόλας. #Include "stdafx.h" #include << "ptrvalue = " \u003c\u003c * PTRVALUE \u003c\u003c ENDL; Διαγράψτε ptrvalue; // Απελευθέρωση μνήμης συστήματος ("pause"); return 0; }

// Κωδικός κώδικα :: Blocks

// Κωδικός DEV-C ++

// new_delete.cpp: Καθορίζει το σημείο εισόδου για την εφαρμογή κονσόλας. #Περιλαμβάνω. Χρησιμοποιώντας το Namespace STD. Int main (int argc, char * agnv) (int * ptrvalue \u003d νέα int; // δυναμική κατανομή μνήμης κάτω από έναν τύπο αντικειμένου int * ptrvalue \u003d 9; // αρχικοποίηση ενός αντικειμένου μέσω δείκτη // int * ptrvalue \u003d new int (9) · Η αρχικοποίηση μπορεί να πραγματοποιηθεί αμέσως όταν δηλώνεται ο εξώφυλος του δυναμικού αντικειμένου<< "ptrvalue = " \u003c\u003c * PTRVALUE \u003c\u003c ENDL; Διαγράψτε ptrvalue; // Απελευθέρωση της μνήμης επιστροφής 0; ) \u003c/ P\u003e \u003cP\u003e (! Lang: In σειρά 10 Η μέθοδος ανακοίνωσης και η αρχικοποίηση του εννέα δυναμικού αντικειμένου παρουσιάζεται, το μόνο που χρειάζεται για τον καθορισμό της τιμής σε στρογγυλά στηρίγματα μετά τον τύπο δεδομένων. Το αποτέλεσμα του προγράμματος παρουσιάζεται στο σχήμα 1.

Ptrvalue \u003d 9 για να συνεχίσετε, πατήστε οποιοδήποτε πλήκτρο. . .

Εικόνα 1 - Δυναμική μεταβλητή

Δημιουργία δυναμικών συστοιχιών

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

// Ανακοίνωση μιας μονοδιάστατης δυναμικής συστοιχίας για 10 στοιχεία: float * ptrarray \u003d νέος πλωτήρας; // όπου το ptrarray είναι ένας δείκτης σε μια ειδική περιοχή μνήμης για μια σειρά από πραγματικούς αριθμούς τύπου float // σε τετράγωνα αγκύλες υποδεικνύουν το μέγεθος του πίνακα

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

// Αφαίρεση της μνήμης της ανάθεσης στο πλαίσιο της μονοδιάστατης δυναμικής συστοιχίας: Διαγράψτε ptrarray;

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

// new_delete_array.cpp: Καθορίζει το σημείο εισόδου για την εφαρμογή της κονσόλας. #Περιλαμβάνω."stdafx.h" #include !} // στο αρχείο κεφαλίδας // στο αρχείο κεφαλίδας < 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

// Κωδικός κώδικα :: Blocks

// Κωδικός DEV-C ++

// new_delete_array.cpp: Καθορίζει το σημείο εισόδου για την εφαρμογή της κονσόλας. #Περιλαμβάνω. // στο αρχείο κεφαλίδας Περιέχει το χρόνο λειτουργίας του πρωτοτύπου () #include // στο αρχείο κεφαλίδας Περιέχει το Prototype Λειτουργία SetPrcision () #include #Περιλαμβάνω. Χρησιμοποιώντας το Namespace STD. Int Main (Int Argc, Char * Argv) (Srand (χρόνος (0)); // Δημιουργία τυχαίων αριθμών float * ptrarray \u003d νέος πλωτήρας; // Δημιουργία μιας δυναμικής σειράς πραγματικών αριθμών για δέκα στοιχεία για (int count \u003d 0 ; Μετρώ< 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

Η δημιουργημένη μονοδιάστατη δυναμική συστοιχία γεμίζει από τυχαίους πραγματικούς αριθμούς που λαμβάνονται από τις λειτουργίες της δημιουργίας τυχαίων αριθμών και οι αριθμοί παράγονται στην περιοχή από 1 έως 10, το διάστημα έχει ρυθμιστεί έτσι ώστε να είναι 10 + 1 . Για να αποκτήσετε τυχαίους πραγματικούς αριθμούς, πραγματοποιείται μια λειτουργία διαίρεσης χρησιμοποιώντας μια ρητή λύση στον πραγματικό τύπο παρονομαστή - πλωτήρα ((rand ()% 10 + 1)). Για να εμφανίσετε μόνο δύο δεκαδικά συμπτώματα χρησιμοποιώντας τη λειτουργία SetPrcision (2) , Το πρωτότυπο αυτής της δυνατότητας βρίσκεται στο αρχείο κεφαλίδας. . Η λειτουργία (0) η λειτουργία στέλνει μια γεννήτρια τυχαίων αριθμών από μια προσωρινή τιμή, έτσι αποδεικνεκτάνεται ότι αναπαράγει την τυχαιότητα της εμφάνισης αριθμών (βλέπε σχήμα 2).

Array \u003d 0,8 0,25 0,86 0,5 2.2 10 1.2 0.33 0.89 3.5 Για να συνεχίσετε, πατήστε οποιοδήποτε πλήκτρο. . .

Εικόνα 2 - Δυναμική συστοιχία στο C ++

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

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

// Ανακοίνωση μιας δισδιάστατης δυναμικής συστοιχίας για 10 στοιχεία: float ** ptrarray \u003d νέος πλωτήρας *; // δύο γραμμές στα έντυπα για (int count \u003d 0;< 2; count++) ptrarray = new float ; // и пять столбцов // где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float

Πρώτον, ο δείκτης δεύτερης τάξης float ** ptrarray ανακοινώνεται, η οποία αναφέρεται σε μια σειρά από δείκτες float *, όπου το μέγεθος του πίνακα είναι δύο . Μετά από αυτό, στον κύκλο C για κάθε σειρά του πίνακα που δηλώθηκε στο Σειρά 2.Υπάρχει μνήμη για πέντε στοιχεία. Ως αποτέλεσμα, λαμβάνεται μια δισδιάστατη δυναμική συστοιχία ptrarray. Περάστε ένα παράδειγμα της μνήμης της μνήμης για μια δισδιάστατη δυναμική συστοιχία.

// Αφαιρέστε τη μνήμη της ανάθεσης κάτω από μια δισδιάστατη δυναμική συστοιχία: για (int count \u003d 0; μέτρηση< 2; count++) delete ptrarray; // где 2 – количество строк в массиве

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

// new_delete_array2.cpp: Καθορίζει το σημείο εισόδου για την εφαρμογή της κονσόλας. #Include "stdafx.h" #include #Περιλαμβάνω. #Περιλαμβάνω. < 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

// Κωδικός κώδικα :: Blocks

// Κωδικός DEV-C ++

// new_delete_array2.cpp: Καθορίζει το σημείο εισόδου για την εφαρμογή της κονσόλας. #Περιλαμβάνω. #Περιλαμβάνω. #Περιλαμβάνω. #Περιλαμβάνω. Χρησιμοποιώντας το Namespace STD. Int Main (Int Argc, Char * ARGV) (SRAND (χρόνος (0)); // Δημιουργία τυχαίων αριθμών // Δυναμική δημιουργία μιας δισδιάστατης σειράς πραγματικών αριθμών για δέκα στοιχεία ** ptrarray *. // δύο σειρές στη συστοιχία για (Int Count \u003d 0; Count< 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

Όταν προέρχεται ο πίνακας, η λειτουργία SETW () χρησιμοποιήθηκε εάν δεν ξεχάσατε, τότε παίρνει τη θέση του καθορισμένου μεγέθους κάτω από τα δεδομένα εξόδου. Στην περίπτωσή μας, για κάθε στοιχείο της σειράς τεσσάρων θέσεων, σας επιτρέπει να ευθυγραμμίσετε, σε στήλες, αριθμούς διαφορετικών μηκών (βλ. Σχήμα 3).

2.7 10 0.33 3 1.4 6 0.67 0.86 1.2 0.44 Για να συνεχίσετε, πατήστε οποιοδήποτε πλήκτρο. . .

Εικόνα 3 - Δυναμική συστοιχία στο C ++

Συνεχίζοντας το θέμα:
Λειτουργός

Έχω μια υπηρεσία Web που εγγράφηκα μέσω "Προσθέστε έναν σύνδεσμο στην υπηρεσία" για την οποία απαιτείται https και πιστοποιητικό. Παρακάτω είναι ο κώδικας μου για τη δημιουργία μιας στιγμής ...

Νέα άρθρα
/
Δημοφιλής