Per citare lo standard del linguaggio C ++, "La durata dell'archiviazione è la proprietà di un oggetto che definisce la durata minima potenziale della memoria contenente l'oggetto." Fondamentalmente, è ciò che ti dice da quanto tempo ti aspetti che una variabile sia utilizzabile. La variabile potrebbe essere un tipo fondamentale, ad esempio un int o un tipo complesso, ad esempio una classe. Indipendentemente dal suo tipo, una variabile è garantita per durare fino a quando il linguaggio di programmazione dice che dovrebbe.
C ++ gestisce la memoria in modo molto diverso da C #. Per prima cosa, non è necessario avere un garbage collector e poche implementazioni ne forniscono uno. Nella misura in cui le implementazioni di C ++ hanno la gestione automatica della memoria, lo fanno per lo più tramite puntatori intelligenti e conteggio dei riferimenti. Le classi C ++ non vivono automaticamente su un heap (gestito da GC o in altro modo). Invece, funzionano molto più come strutture in C #.
Puoi spingere una istanza di classe C ++ su un heap quando è necessario, ma se la dichiari localmente e non fai nulla di divertente, allora avrà una durata automatica, tipicamente implementata usando uno stack, e verrà automaticamente distrutta quando il il programma lascia lo scope in cui esiste la classe.
C ++ ti dà più controllo sulla gestione della memoria di C #. Una conseguenza di ciò è che il linguaggio C ++ e l'ambiente runtime non possono fare altrettanto per impedire codice erroneo come il linguaggio C # e il CLR. Una delle chiavi per essere un buon programmatore C ++ è capire come funziona la gestione della memoria e utilizzare le migliori pratiche per scrivere codice efficiente e corretto.
Le variabili globali, comprese quelle all'interno di spazi dei nomi e le variabili contrassegnate con la parola chiave duration static hanno una durata di archiviazione statica.
Le variabili globali vengono inizializzate durante l'inizializzazione del programma (ovvero il periodo prima che il programma inizi effettivamente l'esecuzione della funzione main o wmain). Vengono inizializzati nell'ordine in cui sono definiti nel codice sorgente. Generalmente non è una buona idea fare affidamento sull'ordine di inizializzazione dal momento che il refactoring e altre modifiche apparentemente innocenti potrebbero facilmente introdurre un potenziale bug nel programma.
Le statiche locali sono inizializzate a zero e l'esecuzione del primo programma raggiunge il blocco contenente la statica locale. In genere, verranno inizializzati sui valori specificati o inizializzati chiamando il costruttore specificato in quel punto. L'assegnazione del valore o la fase di costruzione non è richiesta fino a quando il programma raggiunge ed esegue la dichiarazione, tranne in circostanze molto rare. Una volta inizializzata una statica locale, l'inizializzazione specificata con la sua dichiarazione non verrà mai più eseguita. Questo, ovviamente, è proprio quello che ci aspetteremmo da una statica locale. Se continuava a inizializzarsi ogni volta che il programma raggiungeva la sua linea di definizione, allora sarebbe lo stesso di un locale non statico.
È possibile assegnare altri valori alle statistiche globali e locali, a meno che non vengano anche resi costanti.
All'interno di un blocco, un oggetto ha una durata automatica se viene definito senza il nuovo operatore per istanziarlo e senza una parola chiave di durata di archiviazione, sebbene possa avere facoltativamente la parola chiave register. Ciò significa che l'oggetto viene creato nel momento in cui viene definito e distrutto quando il programma esce dal blocco in cui è stata dichiarata la variabile o quando viene assegnato un nuovo valore alla sua variabile.
Nota: La parola chiave automatica era un modo per selezionare esplicitamente la durata di archiviazione automatica. In C ++ 11, quell'uso è stato rimosso. Ora è l'equivalente della parola chiave var in C #. Se provi a compilare qualcosa usando il vecchio significato di auto, riceverai un errore del compilatore poiché auto come specificatore di tipo deve essere l'unico identificatore di tipo.
La durata dinamica è il risultato dell'utilizzo del nuovo operatore o del nuovo operatore []. Il nuovo operatore viene utilizzato per allocare singoli oggetti, mentre il nuovo operatore [] viene utilizzato per allocare gli array dinamici. È necessario tenere traccia delle dimensioni di una matrice allocata dinamicamente. Mentre l'implementazione C ++ libererà correttamente una matrice allocata dinamicamente, a condizione che si usi l'operatore delete [], non esiste un modo facile o portabile per determinare la dimensione di tale allocazione. Sarà probabilmente impossibile. Gli oggetti singoli vengono liberati con l'operatore di cancellazione.
Quando si assegna memoria utilizzando nuovo o nuovo [], il valore di ritorno è un puntatore. Un puntatore è una variabile che contiene un indirizzo di memoria. In C #, se imposti tutti i riferimenti a un oggetto su null o su qualche altro valore, la memoria non è più raggiungibile nel tuo programma, quindi il GC può liberare quella memoria per altri usi.
In C ++, se si impostano tutti i puntatori su un oggetto su nullptr o qualche altro valore, e non si riesce a capire l'indirizzo originale usando l'aritmetica del puntatore, allora si è persa la possibilità di rilasciare quella memoria usando gli operatori delete o delete [] . Hai quindi creato una perdita di memoria. Se un programma perde abbastanza memoria, alla fine si bloccherà perché il sistema esaurirà gli indirizzi di memoria per esso. Ancor prima, tuttavia, il computer rallenterà in modo orribile, poiché è costretto ad aumentare il paging per adattarsi all'impatto sempre crescente della memoria del tuo programma (supponendo che abbia memoria virtuale, che è assente dalla maggior parte dei telefoni intelligenti).
Nota: Un puntatore const, come someStr nell'istruzione const wchar_t * someStr = L "Hello World!";
non è un puntatore dinamico di durata. Quella memoria è solo una parte del programma stesso. Se provi a chiamare delete o delete [] su di esso, il programma si bloccherà semplicemente. Una stringa è un array di caratteri, tuttavia, se fosse opportuno eliminarlo, l'operatore delete [] sarebbe quello corretto da usare.
Quando si ha a che fare con la memoria dinamica, per eliminare potenziali perdite e limitare la possibilità di altri bug correlati, si dovrebbe sempre usare un puntatore intelligente come std :: unique_ptr
o std :: shared_ptr
. Ne discuteremo nel capitolo che tratta i puntatori.
La durata del thread è la durata di archiviazione meno comunemente utilizzata. È stato solo di recente standardizzato. Al momento della stesura di questo manuale, pochi, se non nessuno, produttori di compilatori C ++ hanno implementato il supporto per la nuova parola chiave thread_local dallo standard C ++ 11.
Questo è certo di cambiare, ma per ora puoi usare estensioni specifiche del fornitore come l'estensione specifica Microsoft _declspec (thread) o l'estensione specifica di GCC __thread se hai bisogno di funzionalità di questo tipo.
La durata del thread è simile alla durata statica tranne che per la durata del programma, queste variabili sono locali per ogni thread; la copia del thread esiste per la durata del thread. L'istanza di ogni thread di un oggetto durata thread viene inizializzata quando viene utilizzata per la prima volta nel thread e viene distrutta quando il thread viene chiuso. Un oggetto durata thread non eredita il suo valore dal thread che ha avviato il thread in cui esiste.
La durata di archiviazione automatica è solitamente la giusta forma di durata di archiviazione per gli oggetti, a meno che non siano necessari per sopravvivere all'ambito in cui sono stati creati. In tal caso, è necessario selezionare una delle durate di archiviazione rimanenti più adatte alle proprie esigenze.
È possibile deviare da tali raccomandazioni se ciò ha senso, ma nella maggior parte dei casi, questa guida ti guiderà correttamente.
Il seguente esempio è incluso per aiutare a chiarire questi concetti. Il campione è molto documentato, quindi non sono inclusi commenti aggiuntivi. Vi incoraggio vivamente a creare ed eseguire questo particolare campione. Vedere l'output mentre passi attraverso il codice ti aiuterà a cogliere questi concetti più facilmente della semplice lettura del codice.
Esempio: StorageDurationSample \ SomeClass.h
#pragma once #include#includere class SomeClass public: explicit SomeClass (int value = 0); SomeClass (valore int, const wchar_t * stringId); ~ SomeClass (void); int GetValue (void) return m_value; void SetValue (valore int) m_valore = valore; static std :: unique_ptr s_someClass; privato: int m_value; std :: wstring m_stringId; ;
Esempio: StorageDurationSample \ SomeClass.cpp
#include "SomeClass.h" # include#includere #includere #includere #includere #includere #includere usando lo spazio dei nomi std; SomeClass :: SomeClass (valore int): m_value (valore), m_stringId (L "(Nessun ID stringa fornito.)") SomeClass * localThis = this; auto addr = reinterpret_cast (localThis); wcout << L"Creating SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Value is '" << m_value << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; SomeClass::SomeClass( int value, const wchar_t* stringId ) : m_value(value), m_stringId(stringId) SomeClass* localThis = this; auto addr = reinterpret_cast (localThis); wcout << L"Creating SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Value is '" << m_value << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; SomeClass::~SomeClass(void) // This is just here to clarify that we aren't deleting a // new object when we replace an old object with it, despite // the order in which the Creating and Destroying output is // shown. m_value = 0; SomeClass* localThis = this; auto addr = reinterpret_cast (localThis); wcout << L"Destroying SomeClass instance." << endl << L"StringId: " << m_stringId.c_str() << L"." << endl << L"Address is: '0x" << setw(8) << setfill(L'0') << hex << addr << dec << L"'." << endl << L"Thread id: '" << this_thread::get_id() << L"'." << endl << endl; // Note that when creating a static member variable, the definition also // needs to have the type specified. Here, we start off with // 'unique_ptr 'prima di procedere al //' SomeClass :: s_someClass = ...; ' assegnazione del valore. unique_ptr SomeClass :: s_someClass = unique_ptr (new SomeClass (10, L "s_someClass"));
Esempio: StorageDurationSample \ StorageDurationSample.cpp
#includere#includere #includere #includere #includere #includere #include "SomeClass.h" #include "... /pchar.h" usando namespace std; struct SomeStruct int Value; ; namespace Value // Visual C ++ non supporta thread_local a partire da VS 2012 RC. Possiamo // mimare parzialmente thread_local con _declspec (thread), ma non possiamo // avere cose come classi con funzioni (inclusi costruttori // e distruttori) con _declspec (thread). _declspec (thread) SomeStruct ThreadLocalSomeStruct = ; // g_staticSomeClass ha durata statica. Esiste fino a quando il programma // termina o finché non viene assegnato un valore diverso. Anche se hai lasciato // fuori dalla parola chiave statica, in questo caso sarebbe ancora statico poiché // non è una variabile locale, non è dinamico e non è una variabile locale thread- //. static SomeClass g_staticSomeClass = SomeClass (20, L "g_staticSomeClass"); // Questo metodo crea un'istanza SomeClass e quindi modifica il valore //. void ChangeAndPrintValue (int value) // Crea una stringa identificativa. wstringstream wsStr (L ""); wsStr << L"ChangeAndPrintValue thread id: '" << this_thread::get_id() << L"'"; // Create a SomeClass instance to demonstrate function-level block scope. SomeClass sc(value, wsStr.str().c_str()); // Demonstrate _declspec(thread). wcout << L"Old value is " << Value::ThreadLocalSomeStruct.Value << L". Thread id: '" << this_thread::get_id() << L"'." << endl; Value::ThreadLocalSomeStruct.Value = value; wcout << L"New value is " << Value::ThreadLocalSomeStruct.Value << L". Thread id: '" << this_thread::get_id() << L"'." << endl; void LocalStatic(int value) static SomeClass sc(value, L"LocalStatic sc"); //// If you wanted to reinitialize sc every time, you would have to //// un-comment the following line. This, however, would defeat the //// purpose of having a local static. You could do something //// similar if you wanted to reinitialize it in certain circumstances //// since that would justify having a local static. //sc = SomeClass(value, L"LocalStatic reinitialize"); wcout << L"Local Static sc value: '" << sc.GetValue() << L"'." << endl << endl; int _pmain(int /*argc*/, _pchar* /*argv*/[]) // Automatic storage; destroyed when this function ends. SomeClass sc1(1, L"_pmain sc1"); wcout << L"sc1 value: '" << sc1.GetValue() << L"'." << endl << endl; // The braces here create a new block. This means that // sc2 only survives until the matching closing brace, since // it also has automatic storage. SomeClass sc2(2, L"_pmain sc2"); wcout << L"sc2 value: '" << sc2.GetValue() << L"'." << endl << endl; LocalStatic(1000); // Note: The local static in LocalStatic will not be reinitialized // with 5000. See the function definition for more info. LocalStatic(5000); // To demonstrate _declspec(thread) we change the value of this // thread's Value::ThreadLocalSomeStruct to 20 from its default 0. ChangeAndPrintValue(20); // We now create a new thread that automatically starts and // changes the value of Value::ThreadLocalSomeStruct to 40. If it // were shared between threads, then it would be 20 from the // previous call to ChangeAndPrintValue. But it's not. Instead, it // is the default 0 that we would expect as a result of this being // a new thread. auto thr = thread(ChangeAndPrintValue, 40); // Wait for the thread we just created to finish executing. Note that // calling join from a UI thread is a bad idea since it blocks // the current thread from running until the thread we are calling // join on completes. For WinRT programming, you want to make use // of the PPLTasks API instead. thr.join(); // Dynamic storage. WARNING: This is a 'naked' pointer, which is a very // bad practice. It is here to clarify dynamic storage and to serve // as an example. Normally, you should use either // std::unique_ptr or std::shared_ptr to wrap any memory allocated with // the 'new' keyword or the 'new[]' keyword. SomeClass* p_dsc = new SomeClass(1000, L"_pmain p_dsc"); const std::size_t arrIntSize = 5; // Dynamic storage array. THE SAME WARNING APPLIES. int* p_arrInt = new int[arrIntSize]; // Note that there's no way to find how many elements arrInt // has other than to manually track it. Also note that the values in // arrInt are not initialized (i.e. it's not arrIntSize zeroes, it's // arrIntSize arbitrary integer values). for (int i = 0; i < arrIntSize; i++) wcout << L"i: '" << i << L"'. p_arrInt[i] = '" << p_arrInt[i] << L"'." << endl; // Assign a value of i to this index. p_arrInt[i] = i; wcout << endl; //// If you wanted to zero out your dynamic array, you could do this: //uninitialized_fill_n(p_arrInt, arrIntSize, 0); for (int i = 0; i < arrIntSize; i++) wcout << L"i: '" << i << L"'. p_arrInt[i] = '" << p_arrInt[i] << L"'." << endl; // If you forgot this, you would have a memory leak. delete p_dsc; //// If you un-commented this, then you would have a double delete, //// which would crash your program. //delete p_dsc; //// If you did this, you would have a program error, which may or may //// not crash your program. Since dsc is not an array, it should not //// use the array delete (i.e. delete[]), but should use the non-array //// delete shown previously. //delete[] p_dsc; // You should always set a pointer to nullptr after deleting it to // prevent any accidental use of it (since what it points to is unknown // at this point). p_dsc = nullptr; // If you forgot this, you would have a memory leak. If you used // 'delete' instead of 'delete[]' unknown bad things might happen. Some // implementations will overlook it while others would crash or do who // knows what else. delete[] p_arrInt; p_arrInt = nullptr; wcout << L"Ending program." << endl; return 0;
Per chi è scomodo eseguire l'esempio, ecco l'output che ottengo quando eseguo questo comando da un prompt dei comandi di Windows 8 Release Preview, compilato con Visual Studio 2012 Ultimate RC in configurazione debug che punta al chipset x86. Probabilmente produrrai valori diversi per gli indirizzi e gli ID di thread se lo esegui sul tuo sistema.
Creazione dell'istanza SomeClass. StringId: s_someClass. L'indirizzo è: '0x009fade8'. Il valore è '10'. ID discussione: '3660'. Creazione dell'istanza SomeClass. StringId: g_staticSomeClass. L'indirizzo è: '0x013f8554'. Il valore è '20'. ID discussione: '3660'. Creazione dell'istanza SomeClass. StringId: _pmain sc1. L'indirizzo è: '0x007bfe98'. Il valore è '1'. ID discussione: '3660'. valore sc1: '1'. Creazione dell'istanza SomeClass. StringId: _pmain sc2. L'indirizzo è: '0x007bfe70'. Il valore è '2'. ID discussione: '3660'. valore sc2: '2'. Distruggere l'istanza di SomeClass. StringId: _pmain sc2. L'indirizzo è: '0x007bfe70'. ID discussione: '3660'. Creazione dell'istanza SomeClass. StringId: LocalStatic sc. L'indirizzo è: '0x013f8578'. Il valore è '1000'. ID discussione: '3660'. Valore sc di Statico locale: '1000'. Valore sc di Statico locale: '1000'. Creazione dell'istanza SomeClass. StringId: ID thread ChangeAndPrintValue: '3660'. L'indirizzo è: '0x007bfbf4'. Il valore è '20'. ID discussione: '3660'. Il vecchio valore è 0. ID thread: '3660'. Il nuovo valore è 20. ID thread: '3660'. Distruggere l'istanza di SomeClass. StringId: ID thread ChangeAndPrintValue: '3660'. L'indirizzo è: '0x007bfbf4'. ID discussione: '3660'. Creazione dell'istanza SomeClass. StringId: ID thread ChangeAndPrintValue: '5796'. L'indirizzo è: '0x0045faa8'. Il valore è '40'. ID discussione: '5796'. Il vecchio valore è 0. ID thread: '5796'. Il nuovo valore è 40. ID thread: '5796'. Distruggere l'istanza di SomeClass. StringId: ID thread ChangeAndPrintValue: '5796'. L'indirizzo è: '0x0045faa8'. ID discussione: '5796'. Creazione dell'istanza SomeClass. StringId: _pmain p_dsc. L'indirizzo è: '0x009fbcc0'. Il valore è '1000'. ID discussione: '3660'. i: '0'. p_arrInt [i] = '-842150451'. i: '1'. p_arrInt [i] = '-842150451'. i: '2'. p_arrInt [i] = '-842150451'. i: '3'. p_arrInt [i] = '-842150451'. i: '4'. p_arrInt [i] = '-842150451'. i: '0'. p_arrInt [i] = '0'. i: '1'. p_arrInt [i] = '1'. i: '2'. p_arrInt [i] = '2'. i: '3'. p_arrInt [i] = '3'. i: '4'. p_arrInt [i] = '4'. Distruggere l'istanza di SomeClass. StringId: _pmain p_dsc. L'indirizzo è: '0x009fbcc0'. ID discussione: '3660'. Programma finale. Distruggere l'istanza di SomeClass. StringId: _pmain sc1. L'indirizzo è: '0x007bfe98'. ID discussione: '3660'. Distruggere l'istanza di SomeClass. StringId: LocalStatic sc. L'indirizzo è: '0x013f8578'. ID discussione: '3660'. Distruggere l'istanza di SomeClass. StringId: g_staticSomeClass. L'indirizzo è: '0x013f8554'. ID discussione: '3660'. Distruggere l'istanza di SomeClass. StringId: s_someClass. L'indirizzo è: '0x009fade8'. ID discussione: '3660'.
La durata dell'archiviazione è un altro aspetto importante del C ++, quindi assicurati di avere una buona conoscenza di ciò che abbiamo discusso in questo articolo prima di proseguire. I prossimi sono costruttori, distruttori e operatori.
Questa lezione rappresenta un capitolo di C ++, un eBook gratuito del team di Syncfusion.