avatar of 发明者量化-小小梦 发明者量化-小小梦
Suivre Messages privés
4
Suivre
1271
Abonnés

Points de connaissance sur la rédaction de stratégies C++ : recyclage de la mémoire C++

Créé le: 2017-12-29 11:00:02, Mis à jour le: 2017-12-29 11:25:11
comments   0
hits   3111

Points de connaissance sur la rédaction de stratégies C++ : recyclage de la mémoire C++

Avant de commencer à écrire une stratégie en C++, il faut avoir quelques connaissances de base, et non pas une maîtrise de Confucius, au moins ces règles. Ci-dessous, une copie de la vidéo:

  • #### Bataille des objets de mémoire en C++

  Si quelqu’un se prétend un expert en programmation mais ne sait rien de la mémoire, je peux vous dire qu’il doit être en train de se vanter. En écrivant en C ou en C++, il faut accorder plus d’attention à la mémoire, non seulement parce que l’allocation raisonnable de la mémoire affecte directement l’efficacité et les performances du programme, mais surtout parce que des problèmes peuvent survenir par inadvertance lorsque nous manipulons la mémoire, et que ces problèmes sont souvent subtils, tels que des fuites de mémoire, comme une aiguille suspendue. L’auteur n’est pas ici aujourd’hui pour discuter de la façon d’éviter ces problèmes, mais pour connaître les objets de mémoire C++ sous un autre angle.

Nous savons que le C++ divise la mémoire en trois zones logiques: les zones de stockage de la pile, de l’échafaudage et de l’état statique. Comme c’est le cas, j’appelle les objets qui se trouvent entre elles des objets de pile, des objets d’état statique et des objets d’état statique.

  • 1 Les concepts de base

    Pour commencer, voyons . , qui est généralement utilisé pour stocker des variables ou des objets locaux, comme ceux que nous déclarons dans la définition d’une fonction avec une expression similaire à la suivante:

    Type stack_object ; 
    

    stack_object est un objet de pile dont la vie commence à partir du point de définition et se termine lorsque la fonction est renvoyée.

    De plus, presque tous les objets provisoires sont des objets de l’argile. Par exemple, la définition de la fonction suivante:

    Type fun(Type object);
    

    Cette fonction génère au moins deux objets provisoires, d’abord, les arguments sont passés par valeur, donc la fonction de construction de copie est appelée pour générer un objet provisoire objet_copy1, qui est utilisé à l’intérieur de la fonction n’est pas objet, mais objet_copy1, naturellement, objet_copy1 est un objet en forme de crochet, qui est libéré lorsque la fonction est renvoyée; et cette fonction est une valeur renvoyée, lorsque la fonction est renvoyée, si nous ne prenons pas en compte l’optimisation de la valeur de retour ((NRV), alors un objet provisoire objet_copy2 est également produit, qui est libéré pendant une période de temps après le retour de la fonction. Par exemple, une fonction a le code suivant:

    Type tt ,result ; //生成两个栈对象
    
    tt = fun(tt); //函数返回时,生成的是一个临时对象object_copy2
    

    L’exécution de la deuxième expression ci-dessus est la suivante: d’abord, la fonction fun génère un objet temporaire object_copy2 à son retour, puis appelle l’opérateur d’attribution pour l’exécuter.

    tt = object_copy2 ; //调用赋值运算符
    

    Vous voyez ? Le compilateur génère tellement d’objets temporaires pour nous sans que nous en soyons conscients, et la dépense de temps et d’espace pour générer ces objets temporaires peut être énorme, donc vous pouvez peut-être comprendre pourquoi il est préférable de passer des paramètres de fonction avec des références const au lieu de passer des paramètres de fonction par valeur.

    En C++, la création et la destruction de tous les objets de la pile sont de la responsabilité du programmeur, donc si elle est mal gérée, il y a des problèmes de mémoire. Si l’objet de la pile est alloué et que vous oubliez de le libérer, il y aura une fuite de mémoire; et si l’objet a été libéré, mais que vous n’avez pas mis le pointeur correspondant à NULL, le pointeur est appelé le pointeur suspendu, et l’utilisation de ce pointeur à nouveau entraînera des accès illégaux, ce qui entraînera le crash du programme.

    La seule façon de le faire est d’utiliser new (bien sûr, il est possible d’obtenir de la mémoire de la pile en C avec une instruction de type malloc). En utilisant new, une partie de la mémoire est allouée dans la pile et un pointeur renvoie à l’objet de la pile.

    En fait, avant que le code d’affichage dans la fonction main () ne soit exécuté, une fonction _main () générée par le compilateur est appelée, tandis que la fonction _main () effectue la construction et l’initialization de tous les objets globaux. Avant la fin de la fonction main () , une fonction exit générée par le compilateur est appelée pour libérer tous les objets globaux. Par exemple, le code suivant:

    void main(void)
    {
        ... // 显式代码
    }
    
    
    // 实际上转化为这样:
    
    
    void main(void)
    {
        _main(); //隐式代码,由编译器产生,用以构造所有全局对象
        ...      // 显式代码
        ...
        exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象
    }
    

    Donc, en sachant cela, on peut en tirer quelques astuces, par exemple, supposons que nous voulions faire des préparatifs avant que la fonction main () ne s’exécute, alors nous pourrions écrire ces préparatifs dans la fonction de construction d’un objet global personnalisé, de sorte que, avant que le code explicatif de la fonction main () ne soit exécuté, la fonction de construction de cet objet global soit appelée et exécute le mouvement attendu, ce qui nous permettrait d’atteindre notre objectif. Nous venons de parler d’objets globaux dans la zone de stockage statique, alors, des objets statiques locaux? Les objets statiques locaux sont également généralement définis dans la fonction, comme les objets en forme d’araignées, mais avec plusieurs mots clés statiques à l’avant.

    Il y a aussi un objet statique, c’est-à-dire qu’il est un membre statique d’une classe. Considérant cette situation, des problèmes plus complexes sont impliqués.

    Le premier problème est la durée de vie des objets membres statiques de la classe, qui sont nés avec la création du premier objet de la classe et disparaissent à la fin de la procédure. C’est-à-dire qu’il existe une situation dans laquelle nous définissons une classe dans laquelle une classe a un objet statique en tant que membre, mais au cours de l’exécution de la procédure, si nous n’avons créé aucun objet de cette classe, nous ne créerons pas l’objet statique que cette classe contient.

    Le deuxième problème est que lorsque:

    class Base
    {
    public:
        static Type s_object ;
    }
    
    
    class Derived1 : public Base / / 公共继承
    {
    
    
        ... // other data 
    
    
    }
    
    
    class Derived2 : public Base / / 公共继承
    {
    
    
        ... // other data 
    
    
    }
    
    
    Base example ;
    
    
    Derivde1 example1 ;
    
    
    Derivde2 example2 ;
    
    
    example.s_object = …… ;
    
    
    example1.s_object = …… ; 
    
    
    example2.s_object = …… ; 
    

    Notez les trois déclarations ci-dessus marquées comme corps noir, s_object est le même objet qu’ils visitent? La réponse est oui, ils sont en effet dirigés vers le même objet, cela ne semble pas vrai, n’est-ce pas? Mais c’est le cas, vous pouvez écrire un simple paragraphe de code pour vérifier vous-même.

    Imaginons que nous donnions un objet de type Derived1 à une fonction qui accepte des paramètres de type Base non-référencés, et que cela provoque une coupe, alors comment est-ce que la coupe a lieu ? Je pense que vous savez maintenant, c’est simplement enlever le sous-objet de l’objet de type Derived1 et ignorer tous les autres membres de données personnalisés par Derived1, puis transmettre ce sous-objet à la fonction (en fait, la fonction utilise une copie de ce sous-objet).

    Tous les objets des classes dérivées de la classe Base héritée contiennent un sous-objet de type Base (c’est-à-dire une clé qui peut être pointée vers un objet Derived1 avec un pointeur de type Base, ce qui est naturellement une clé polymorphe), tandis que tous les sous-objets et tous les objets des types Base partagent le même objet s_object. Naturellement, toutes les instances de la classe dans l’ensemble du système d’héritage dérivé de la classe Base partagent le même objet s_object. La disposition des objets de l’exemple 1, de l’exemple 2 mentionnés ci-dessus est la suivante:

  • 2 Comparaison des trois types d’objets de mémoire

    L’avantage d’un objet en coulisse est qu’il est généré automatiquement au bon moment et détruit automatiquement au bon moment, sans que le programmeur ait à s’en occuper; et la vitesse de création d’un objet en coulisse est généralement plus rapide que celle d’un objet en pile, car lors de l’allocation d’un objet en pile, l’opération de nouvel opérateur est appelée, l’opération de nouvel opérateur utilise un algorithme de recherche d’espace mémoire, et ce processus de recherche peut être très long, produire un objet en coulisse n’est pas si compliqué, il suffit de déplacer le pointeur du sommet de la coulisse. Cependant, il convient de noter que la capacité de l’espace de coulisse est généralement faible, généralement 1MB à 2MB, de sorte que les objets de grande taille ne conviennent pas à une allocation en coulisse.

    Les objets de pile, leur moment de création et leur moment de destruction doivent être définis avec précision par le programmeur, c’est-à-dire que le programmeur a un contrôle total sur la vie des objets de pile. Nous avons souvent besoin d’objets de ce type, par exemple, nous devons créer un objet qui peut être accédé par plusieurs fonctions, mais nous ne voulons pas le rendre global, alors créer un objet de pile est sans aucun doute un bon choix, puis transmettre le pointeur de cet objet de pile entre les fonctions, ce qui permet de partager cet objet.

    Nous allons voir maintenant les objets statiques.

    Les objets globaux sont généralement inexistants dans les langages entièrement orientés objets, comme C#, car ils sont dangereux et hautement codés. L’utilisation excessive d’objets globaux dans un programme réduit considérablement la robustesse, la stabilité, la maintenabilité et la polyvalence du programme. C++ peut également éliminer complètement les objets globaux, mais finalement pas, je pense que c’est pour la compatibilité avec C.

    Ensuite, il y a les membres statiques de la classe. Comme nous l’avons mentionné plus haut, tous les objets de la classe mère et de ses classes dérivées partagent cet objet de membre statique, de sorte qu’un tel membre statique est sans aucun doute un bon choix lorsque vous avez besoin de partager des données ou de communiquer entre ces classes ou entre ces objets de classe.

    Ensuite, il y a les objets locaux statiques, qui sont principalement utilisés pour conserver l’état intermédiaire de l’objet pendant que la fonction est appelée à plusieurs reprises, l’un des exemples les plus remarquables étant la fonction récurrente. Nous savons tous que la fonction récurrente est une fonction qui s’appelle elle-même.

    Dans la conception de fonctions récursives, on peut utiliser des objets statiques pour remplacer des objets locaux non statiques (c’est-à-dire des objets de coque), ce qui permet non seulement de réduire les coûts de génération et de libération des objets non statiques à chaque appel et retour récursifs, mais aussi d’enregistrer l’état intermédiaire de l’appel récursif et d’être accessible pour les couches d’appel.

  • 3 Récolte accidentelle d’objets utilisés pour la cuisson

    Comme nous l’avons vu plus haut, les objets de la souche sont créés au bon moment, puis libérés automatiquement au bon moment, c’est-à-dire qu’ils ont une fonction de gestion automatique. Alors, où les objets de la souche sont-ils libérés automatiquement? Premièrement, à la fin de leur vie; deuxièmement, lorsqu’une anomalie se produit dans la fonction dans laquelle ils sont.

    Le processus réel est le suivant: lorsque la fonction rejette une anomalie, ce que l’on appelle stack_unwinding se produit, c’est-à-dire que la pile s’ouvre, et comme la pile est présente dans la pile naturelle, la fonction de décomposition de l’objet rejeté est exécutée, libérant ainsi la ressource encapsulée. À moins que cette anomalie ne soit rejetée à nouveau au cours de l’exécution de la fonction de décomposition, ce qui est très peu probable, la probabilité de fuite de la ressource est réduite, car la pile peut libérer des ressources automatiquement, même si une anomalie se produit dans la fonction dans laquelle elle se trouve.

  • 4 La création d’objets de pile est interdite

    Comme nous l’avons mentionné plus haut, si vous décidez d’interdire la génération d’un type d’objet de pile, vous pouvez créer une classe d’enveloppe de ressource qui ne peut être générée que dans la pile, ce qui permet de libérer automatiquement les ressources encapsulées dans des cas exceptionnels.

    Nous savons que la seule façon de créer un objet de pile est d’utiliser l’opération new, et si nous l’interdisons, cela ne fonctionnera pas. En outre, l’opération new est appelée à l’exécution de l’opérateur new, et l’opérateur new peut être rechargé.

    #include <stdlib.h> //需要用到C式内存分配函数
    
    
    class Resource ; //代表需要被封装的资源类
    
    
    class NoHashObject
    {
    private: 
        Resource* ptr ;//指向被封装的资源
    
    
        ... ... //其它数据成员
    
    
        void* operator new(size_t size) //非严格实现,仅作示意之用
        { 
            return malloc(size) ; 
        } 
    
    
        void operator delete(void* pp) //非严格实现,仅作示意之用
        { 
            free(pp) ; 
        } 
    
    
    public: 
        NoHashObject() 
        { 
            //此处可以获得需要封装的资源,并让ptr指针指向该资源
    
    
            ptr = new Resource() ; 
        } 
    
    
        ~NoHashObject() 
        { 
    
    
            delete ptr ; //释放封装的资源
        } 
    }; 
    

    NoHashObject est désormais une classe qui interdit les objets de la pile si vous écrivez le code suivant:

    NoHashObject* fp = new NoHashObject() ; // erreur de compilation !

    delete fp ;

    Le code ci-dessus génère des erreurs de compilation. Eh bien, maintenant que vous savez comment concevoir une classe qui interdit les objets de pile, vous vous demandez peut-être, comme moi, si la définition de la classe NoHashObject ne peut pas être modifiée, est-ce que vous ne pouvez pas nécessairement générer ce type d’objets de pile? Non, il y a une méthode, que j’appelle le déchiffrement de la violence de l’acier.

    void main(void)
    {
        char* temp = new char[sizeof(NoHashObject)] ; 
    
    
        //强制类型转换,现在ptr是一个指向NoHashObject对象的指针
    
    
        NoHashObject* obj_ptr = (NoHashObject*)temp ; 
    
    
        temp = NULL ; //防止通过temp指针修改NoHashObject对象
    
    
        //再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员
    
    
        Resource* rp = (Resource*)obj_ptr ; 
    
    
        //初始化obj_ptr指向的NoHashObject对象的ptr成员
    
    
        rp = new Resource() ; 
    
    
        //现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了
    
    
        ... ... 
    
    
        delete rp ;//释放资源
    
    
        temp = (char*)obj_ptr ; 
    
    
        obj_ptr = NULL ;//防止悬挂指针产生
    
    
        delete [] temp ;//释放NoHashObject对象所占的堆空间。
    
    
        } 
    

    La mise en œuvre ci-dessus est gênante, et cette mise en œuvre est rarement utilisée dans la pratique, mais j’ai écrit la voie, parce qu’il est utile de la comprendre pour que nous comprenions les objets de mémoire C++.

    Les données d’un bloc de mémoire sont inchangées, et les types sont les lunettes que nous portons, et quand nous portons un type de lunettes, nous interprétons les données de la mémoire avec les types correspondants, de sorte que différentes interprétations donnent des informations différentes.

    La conversion de type forcée consiste en fait à changer de lunettes et à revoir la même donnée en mémoire.

    Il est également important de noter que la disposition des données de membre d’un objet peut varier d’un compilateur à l’autre. Par exemple, la plupart des compilateurs placent les membres du pointeur ptr de NoHashObject dans les 4 premiers octets de l’espace de l’objet, ce qui garantit que la transformation de la phrase suivante s’exécute comme nous l’espérons:

    Resource* rp = (Resource*)obj_ptr ;

    Cependant, tous les compilateurs ne sont pas nécessairement comme ça.

    Puisque nous pouvons interdire la production d’un certain type d’objets de pile, peut-on concevoir une classe qui ne peut pas produire d’objets de pile ? Bien sûr que oui.

  • 5 La création d’objets en forme d’oiseaux est interdite

    Comme mentionné précédemment, la création d’un objet de coque consiste à déplacer le pointeur de la souris pour extraire de la souris un espace de taille appropriée, puis à appeler directement la fonction de construction correspondante dans cet espace pour former un objet de coque. Lorsque la fonction revient, elle appelle sa fonction de composition pour libérer l’objet, puis ajuste le pointeur de la souris pour récupérer la mémoire de la souris.

    C’est tout à fait possible, et c’est ce que j’ai l’intention d’adopter. Mais avant cela, il y a un point à considérer, c’est-à-dire que si nous définissons la fonction de construction comme privée, nous ne pourrons pas non plus utiliser new pour générer directement des objets de pile, car new appellera sa fonction de construction après avoir alloué de l’espace à l’objet.

    Si une classe n’est pas destinée à être une classe de base, la solution habituelle est de déclarer sa fonction de décomposition comme private.

    Afin de limiter les objets de la colonne sans limiter l’héritage, nous pouvons déclarer la fonction de décomposition comme étant protégée, ce qui est le meilleur des deux. Voici le code suivant:

    class NoStackObject
    {
    protected: 
    
    
        ~NoStackObject() { } 
    
    
    public: 
    
    
        void destroy() 
    
    
        { 
    
    
            delete this ;//调用保护析构函数
    
    
        } 
    }; 
    

    Ensuite, vous pouvez utiliser la classe NoStackObject comme ceci:

    NoStackObject* hash_ptr = new NoStackObject() ;

    … … // effectuer une action sur l’objet dirigé par hash_ptr

    hash_ptr->destroy() ; Eh bien, est-ce que cela semble un peu bizarre que nous ayons créé un objet avec new, mais que nous ne l’ayons pas supprimé avec delete, mais que nous l’ayons détruit. Il est évident que les utilisateurs ne sont pas habitués à ce type d’utilisation étrange. Donc, j’ai décidé de définir la fonction de construction comme privée ou protégée.

    class NoStackObject
    {
    protected: 
    
    
        NoStackObject() { } 
    
    
        ~NoStackObject() { } 
    
    
    public: 
    
    
        static NoStackObject* creatInstance() 
        { 
            return new NoStackObject() ;//调用保护的构造函数
        } 
    
    
        void destroy() 
        { 
    
    
            delete this ;//调用保护的析构函数
    
    
        } 
    };
    

    Vous pouvez maintenant utiliser la classe NoStackObject comme suit:

    NoStackObject* hash_ptr = NoStackObject::creatInstance() ;

    … … // effectuer une action sur l’objet dirigé par hash_ptr

    hash_ptr->destroy() ;

    hash_ptr = NULL ; // empêche l’utilisation du pointeur suspendu

    C’est un peu mieux maintenant, les opérations de génération d’objets et de libération d’objets sont alignées.

  • Les méthodes de récupération de déchets en C++

Beaucoup de programmeurs C ou C++ sont opposés à la récupération des déchets, pensant que la récupération des déchets est certainement moins efficace que la gestion de la mémoire dynamique par eux-mêmes, et que la récupération entraînera l’arrêt du programme, alors que si vous contrôlez la gestion de la mémoire, l’allocation et la libération sont stables et ne provoqueront pas l’arrêt du programme. Enfin, de nombreux programmeurs C/C++ sont convaincus qu’il est impossible de mettre en œuvre un mécanisme de récupération des déchets en C/C++. Ces idées erronées sont le fruit d’une ignorance des algorithmes de récupération des déchets.

En fait, le mécanisme de récupération des déchets n’est pas lent, et même plus efficace que l’allocation dynamique de la mémoire. Comme nous ne pouvons qu’allouer sans libérer, il suffit d’obtenir de nouvelles mémoires de la pile et de déplacer le pointeur du sommet de la pile lors de l’allocation de la mémoire. Le processus de libération est omis et naturellement accéléré.

Le point de vue selon lequel la récupération de la corbeille est impossible en C/C++ est généralement basé sur l’impossibilité de numériser correctement tous les blocs de mémoire qui pourraient encore être utilisés, mais ce qui semble impossible n’est en fait pas compliqué. Tout d’abord, en scannant les données de la mémoire, les pointeurs qui pointent vers la mémoire allouée dynamiquement sur la pile sont facilement identifiables.

Lors de la récupération des déchets, il suffit de scanner les segments bss, data et le coffre-fort actuellement utilisé pour trouver la quantité de pointers de mémoire dynamique qui pourraient être utilisés. La récupération récurrente de la mémoire référencée permet d’obtenir toutes les mémoires dynamiques actuellement utilisées.

Il est possible d’améliorer la gestion de la mémoire et même de réduire la consommation totale de mémoire si l’on implémente un bon récupérateur de déchets pour votre projet. Si vous êtes intéressé, il est particulièrement important pour un programmeur de parcourir les archives et les bibliothèques existantes en ligne sur le recyclage des déchets.

Je suis désolée.HK Zhang

  • #### Pourquoi le cycle de vie d’une variable locale est-il prolongé jusqu’à la fin du programme lorsqu’elle est adressée à un pointeur ?
  #include<stdio.h>
  int*fun(){
      int k = 12;
      return &k;
  }
  int main(){
      int *p = fun();    
      printf("%d\n", *p);

      getchar();
      return 0;
  }

L’accès est non seulement accessible, mais il peut être modifié, mais il n’est pas garanti. L’adresse d’une variable locale se trouve dans la pile du programme lui-même, et après la fin de la variable locale, sa valeur existe toujours tant que l’adresse mémoire de la variable locale n’a pas été donnée à une autre variable. Mais si elle est modifiée, c’est plus dangereux, car cette adresse mémoire peut avoir été donnée à d’autres variables du programme, ce qui peut provoquer le crash du programme si la modification est forcée par le pointeur.

csdn bbs