C ++ succinctement Durée de stockage

introduction

Pour citer le standard de langage C ++, «la durée de stockage est la propriété d'un objet qui définit la durée de vie potentielle minimale du stockage contenant l'objet». En gros, c'est ce qui vous indique le temps pendant lequel une variable doit être utilisable. La variable peut être un type fondamental, tel qu'un int, ou un type complexe, tel qu'une classe. Quel que soit son type, une variable n’est garantie que tant que le langage de programmation le stipule..

C ++ gère la mémoire très différemment de C #. D'une part, il n'est pas nécessaire d'avoir un ramasse-miettes et peu d'implémentations en fournissent un. Dans la mesure où les implémentations C ++ disposent d'une gestion automatique de la mémoire, elles le font principalement via des pointeurs intelligents et un comptage de références. Les classes C ++ ne vivent pas automatiquement sur un segment (géré par le GC ou autre). Au lieu de cela, ils fonctionnent beaucoup plus comme des structures en C #.

Vous pouvez pousser une instance de classe C ++ sur un segment de mémoire lorsque vous le souhaitez, mais si vous la déclarez localement et ne faites rien de drôle, elle aura une durée automatique, généralement mise en œuvre à l'aide d'une pile, et sera automatiquement détruite lorsque programme quitte la portée dans laquelle la classe existe.

C ++ vous donne plus de contrôle sur la gestion de la mémoire que C #. En conséquence, le langage C ++ et l'environnement d'exécution ne peuvent pas empêcher autant de code erroné que le langage C # et le CLR. Pour être un bon programmeur C ++, il est essentiel de comprendre le fonctionnement de la gestion de la mémoire et de suivre les meilleures pratiques pour écrire du code correct et efficace..


Durée statique

Les variables globales, y compris celles se trouvant à l'intérieur des espaces de noms, et les variables marquées avec le mot-clé duration statique ont une durée de stockage statique..

Les variables globales sont initialisées lors de l’initialisation du programme (c’est-à-dire la période précédant l’exécution effective du programme par votre fonction principale ou wmain). Ils sont initialisés dans l'ordre dans lequel ils sont définis dans le code source. Ce n’est généralement pas une bonne idée de s’appuyer sur l’ordre d’initialisation, car la refactorisation et d’autres modifications apparemment innocentes pourraient facilement introduire un bogue potentiel dans votre programme..

La statique locale est initialisée à zéro la première fois que l'exécution du programme atteint le bloc contenant la statique locale. En règle générale, ils seront initialisés à leurs valeurs spécifiées ou initialisés en appelant le constructeur spécifié à ce stade. La phase d’affectation de valeur ou de construction n’est nécessaire que lorsque le programme a atteint et exécuté l’instruction, sauf dans de très rares circonstances. Une fois qu'une statique locale est initialisée, l'initialisation spécifiée avec sa déclaration ne sera plus jamais exécutée. Ceci, bien sûr, correspond exactement à ce que nous attendions d’un statique local. S'il continuait à s'initialiser chaque fois que le programme atteignait sa ligne de définition, ce serait identique à un fichier local non statique..

Vous pouvez affecter d'autres valeurs aux statiques globales et locales, sauf si vous les rendez également const, bien sûr..


Durée automatique

Dans un bloc, un objet a une durée automatique s'il est défini sans l'opérateur new pour l'instancier, et sans mot clé de durée de stockage, bien qu'il puisse éventuellement avoir le mot clé register. Cela signifie que l'objet est créé au moment où il est défini et qu'il est détruit lorsque le programme quitte le bloc dans lequel sa variable a été déclarée ou lorsqu'une nouvelle valeur est affectée à sa variable..

Remarque: Le mot-clé auto était un moyen de sélectionner explicitement la durée de stockage automatique. En C ++ 11, cette utilisation a été supprimée. C'est maintenant l'équivalent du mot clé var en C #. Si vous essayez de compiler quelque chose en utilisant l'ancien sens de auto, vous recevrez une erreur de compilation, car auto en tant que spécificateur de type doit être le seul spécificateur de type..


Durée dynamique

La durée dynamique résulte de l'utilisation du nouvel opérateur ou du nouvel opérateur []. L'opérateur new est utilisé pour allouer des objets individuels, tandis que le nouvel opérateur [] est utilisé pour allouer des tableaux dynamiques. Vous devez suivre la taille d'un tableau alloué dynamiquement. Bien que l'implémentation C ++ libère correctement un tableau alloué de manière dynamique, à condition que vous utilisiez l'opérateur delete [], il n'existe aucun moyen simple ou portable de déterminer la taille de cette allocation. Ce sera probablement impossible. Les objets simples sont libérés avec l'opérateur delete.

Lorsque vous allouez de la mémoire en utilisant new ou new [], la valeur de retour est un pointeur. Un pointeur est une variable qui contient une adresse mémoire. En C #, si vous définissez toutes vos références à un objet sur null ou une autre valeur, la mémoire n'est plus accessible dans votre programme. Le CPG peut donc libérer cette mémoire pour d'autres utilisations..

En C ++, si vous définissez tous vos pointeurs sur un objet avec nullptr ou une autre valeur et que vous ne pouvez pas déterminer l'adresse d'origine à l'aide de l'arithmétique de pointeur, vous avez perdu votre capacité à libérer cette mémoire à l'aide des opérateurs delete ou delete []. . Vous avez ainsi créé une fuite de mémoire. Si un programme perd suffisamment de mémoire, il finira par tomber en panne car le système manquera d'adresses mémoire. Même avant cela, l'ordinateur ralentira terriblement, car il est obligé d'augmenter la pagination pour tenir compte de l'empreinte mémoire grandissante de votre programme (en supposant qu'il dispose d'une mémoire virtuelle, absente de la plupart des téléphones intelligents)..

Remarque: Un pointeur const, tel que someStr dans l'instruction const wchar_t * someStr = L "Bonjour le monde!"; n'est pas un pointeur de durée dynamique. Cette mémoire est juste une partie du programme lui-même. Si vous essayez d'appeler delete ou delete [] dessus, le programme va simplement planter. Une chaîne de caractères est cependant un tableau de caractères. Par conséquent, si vous pouviez le supprimer, alors l'opérateur delete [] serait le bon à utiliser..

Lorsque vous traitez avec de la mémoire dynamique, pour éviter les fuites potentielles et limiter les risques d’autres bogues, utilisez toujours un pointeur intelligent, tel que std :: unique_ptr ou std :: shared_ptr. Nous en discuterons dans le chapitre qui couvre les pointeurs.


Durée du fil

La durée du fil est la durée de stockage la moins couramment utilisée. Il n'a été que récemment normalisé. Au moment d'écrire ces lignes, peu de vendeurs de compilateurs C ++, s'il en est, ont implémenté la prise en charge du nouveau mot clé thread_local à partir du standard C ++ 11..

Cela va certainement changer, mais pour le moment, vous pouvez utiliser des extensions spécifiques au fournisseur, telles que l'extension _declspec (thread) spécifique à Microsoft ou l'extension __thread spécifique à GCC, si vous avez besoin de fonctionnalités de ce type..

La durée du thread est similaire à la durée statique, à la différence qu'au lieu de durer toute la vie du programme, ces variables sont locales à chaque thread; la copie du fil existe pour la durée du fil. L'instance de chaque thread d'un objet de durée de thread est initialisée lors de sa première utilisation dans le thread et elle est détruite à la sortie du thread. Un objet de durée de fil n'hérite pas de sa valeur du fil qui a démarré le fil dans lequel il existe.


Choisir la bonne durée de stockage

La durée de stockage automatique est généralement la forme de durée de stockage appropriée pour les objets, sauf si vous en avez besoin pour survivre à l'étendue dans laquelle ils ont été créés. Si tel est le cas, vous devez choisir l'une des durées de stockage restantes qui correspond le mieux à vos besoins..

  • Si l'objet doit exister pendant toute la durée de l'exécution du programme, utilisez une durée de stockage statique..
  • Si l'objet doit exister pour toute la longueur d'un thread particulier, utilisez la durée de stockage du thread..
  • Si l'objet n'existera que pendant une partie de la durée du programme ou du thread, utilisez la durée de stockage dynamique.

Vous pouvez vous écarter de ces recommandations si cela vous convient, mais dans la plupart des cas, ces instructions vous guideront correctement..


Durée de stockage échantillon

L'exemple suivant est inclus pour aider à clarifier ces concepts. L’échantillon étant très documenté, aucun commentaire supplémentaire n’est inclus. Je vous encourage fortement à construire et à exécuter cet exemple particulier. Voir la sortie tout en parcourant le code vous aidera à comprendre ces concepts plus facilement que la simple lecture du code..

Exemple: StorageDurationSample \ SomeClass.h

#pragma fois #include  #comprendre  classe SomeClass public: explicite SomeClass (int value = 0); SomeClass (int value, const wchar_t * stringId); ~ SomeClass (void); int GetValue (void) return m_value;  void SetValue (int value) m_value = value;  statique std :: unique_ptr s_someClass; private: int m_value; std :: wstring m_stringId; ;

Exemple: StorageDurationSample \ SomeClass.cpp

#include "SomeClass.h" #include  #comprendre  #comprendre  #comprendre  #comprendre  #comprendre  #comprendre  using namespace std; SomeClass :: SomeClass (int value): m_value (value), m_stringId (L "(Aucune id de chaîne fournie.)") SomeClass * localThis = this; auto addr = réinterpréter_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'avant de passer à la //' SomeClass :: s_someClass =…; ' cession de valeur. unique_ptr SomeClass :: s_someClass = unique_ptr(new SomeClass (10, L "s_someClass"));

Exemple: StorageDurationSample \ StorageDurationSample.cpp

#comprendre  #comprendre  #comprendre  #comprendre  #comprendre  #comprendre  #include "SomeClass.h" #include "… /pchar.h" en utilisant namespace std; struct SomeStruct int Value; ; namespace Value // Visual C ++ ne prend pas en charge thread_local à partir de VS 2012 RC. Nous pouvons // imiter partiellement thread_local avec _declspec (thread), mais nous ne pouvons pas // avoir de choses en tant que classes avec des fonctions (y compris les constructeurs // et les destructeurs) avec _declspec (thread). _declspec (thread) SomeStruct ThreadLocalSomeStruct = ; // g_staticSomeClass a une durée statique. Il existe jusqu'à la fin du programme ou jusqu'à ce qu'une valeur différente lui soit affectée. Même si vous avez laissé // le mot-clé static, dans ce cas, il serait toujours statique car // ce n'est pas une variable locale, ni dynamique, ni une variable locale thread-//. static SomeClass g_staticSomeClass = SomeClass (20, L "g_staticSomeClass");  // Cette méthode crée une instance de SomeClass, puis modifie la valeur //. void ChangeAndPrintValue (int value) // Crée une chaîne d'identifiant. 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; 

Pour ceux qui ne sont pas à l'aise pour exécuter l'exemple, voici le résultat obtenu lorsque je l'exécute à partir d'une invite de commande sous Windows 8 Release Preview, compilée avec Visual Studio 2012 Ultimate RC dans une configuration Debug ciblant le chipset x86. Vous produirez probablement des valeurs différentes pour les adresses et les ID de thread si vous l'exécutez sur votre propre système..

Création d'une instance SomeClass. StringId: s_someClass. L'adresse est: '0x009fade8'. La valeur est '10'. Identifiant du fil: '3660'. Création d'une instance SomeClass. StringId: g_staticSomeClass. L'adresse est: '0x013f8554'. La valeur est '20'. Identifiant du fil: '3660'. Création d'une instance SomeClass. StringId: _pmain sc1. L'adresse est: '0x007bfe98'. La valeur est '1'. Identifiant du fil: '3660'. valeur sc1: '1'. Création d'une instance SomeClass. StringId: _pmain sc2. L'adresse est: '0x007bfe70'. La valeur est '2'. Identifiant du fil: '3660'. Valeur sc2: '2'. Détruire l'instance SomeClass. StringId: _pmain sc2. L'adresse est: '0x007bfe70'. Identifiant du fil: '3660'. Création d'une instance SomeClass. StringId: LocalStatic sc. L'adresse est: '0x013f8578'. La valeur est '1000'. Identifiant du fil: '3660'. Valeur sc locale statique: '1000'. Valeur sc locale statique: '1000'. Création d'une instance SomeClass. StringId: ID du thread ChangeAndPrintValue: '3660'. L'adresse est: '0x007bfbf4'. La valeur est '20'. Identifiant du fil: '3660'. L'ancienne valeur est 0. Identifiant du fil de discussion: '3660'. La nouvelle valeur est 20. Identifiant de filetage: '3660'. Détruire l'instance SomeClass. StringId: ID du thread ChangeAndPrintValue: '3660'. L'adresse est: '0x007bfbf4'. Identifiant du fil: '3660'. Création d'une instance SomeClass. StringId: ID du thread ChangeAndPrintValue: '5796'. L'adresse est: '0x0045faa8'. La valeur est '40'. Identifiant du fil: '5796'. L'ancienne valeur est 0. Identifiant du fil de discussion: '5796'. La nouvelle valeur est 40. Identifiant du fil de discussion: '5796'. Détruire l'instance SomeClass. StringId: ID du thread ChangeAndPrintValue: '5796'. L'adresse est: '0x0045faa8'. Identifiant du fil: '5796'. Création d'une instance SomeClass. StringId: _pmain p_dsc. L'adresse est: '0x009fbcc0'. La valeur est '1000'. Identifiant du fil: '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'. Détruire l'instance SomeClass. StringId: _pmain p_dsc. L'adresse est: '0x009fbcc0'. Identifiant du fil: '3660'. Fin du programme. Détruire l'instance SomeClass. StringId: _pmain sc1. L'adresse est: '0x007bfe98'. Identifiant du fil: '3660'. Détruire l'instance SomeClass. StringId: LocalStatic sc. L'adresse est: '0x013f8578'. Identifiant du fil: '3660'. Détruire l'instance SomeClass. StringId: g_staticSomeClass. L'adresse est: '0x013f8554'. Identifiant du fil: '3660'. Détruire l'instance SomeClass. StringId: s_someClass. L'adresse est: '0x009fade8'. Identifiant du fil: '3660'.

Conclusiong

La durée de stockage est un autre aspect important de C ++, alors assurez-vous de bien comprendre ce que nous avons discuté dans cet article avant de poursuivre. Ensuite, les constructeurs, les destructeurs et les opérateurs.

Cette leçon représente un chapitre de C ++ Succinctly, un eBook gratuit de l’équipe de Syncfusion..