• [^] # Re: memforget et fuite mémoire

    Posté par . En réponse au journal cTypes + Rust = approfondir une relation d'amour et d'eau (fraîche). Évalué à 2.

    @Maga : voici une proposition de complément de l'article. Si tu l'as l'occasion / la possibilité d'y jeter un coup d’œil... (peur d'une coquille supplémentaire)


    Mea maxima culpa

    Errare humanum est, perseverare diabolicum : j'ai fait une erreur. Pris dans mon élan je n'ai pas évoqué la question de la libération de la mémoire. En réalité, j'avais tort sur ma vision de la gestion de la mémoire par cTypes.

    Mon idée était la suivante : cTypes n'évoque à aucun moment la question de la libération de la mémoire. J'ai supposé que c'est la stratégie de gestion de la mémoire de Python qui s'applique, et donc qu'il était en capacité de prendre en charge pour nous.

    Las, le commentaire de GaMa, tape juste : quoiqu'il arrive ça pêche sur l'usage, car un pointeur renvoie vers une zone qui peut avoir elle-même d'autres pointeurs (voir ici et ). Il faut gérer côté Python cet aspect, en demandant côté Rust dans notre cas, la libération de la mémoire. Et il ne peut pas connaître du reste, toutes les tailles à désallouer (typiquement le tableau mais pour d'autres éléments, il connaît la taille).

    Dans notre bibliothèque Rust, rajoutons donc le nécessaire :

    #[no_mangle]
    pubunsafeextern"C"fn liberer_valeurretour(ptr: *mutValeurRetour){
    drop(Box::from_raw(ptr));
    }

    Côté Python, c'est plus compliqué. On doit gérer désormais la durée de vie de la mémoire derrière le pointeur, mais l'appeler au bon moment pour ne pas désallouer côté Rust et utiliser côté Python.

    Pour cela, le mieux que j'ai trouvé est d'avoir un objet intermédiaire dont on aura le signal de destruction. ("__del__"). C'est lui qui permettra de faire appel à la désallocation :

    print( "--- partie 3.2 - création d'un objet de retour de taille indéterminée à l'appel" ) 
    class ValeurRetour(Structure): # on n'utilise pas directement 
     _fields_ = [ 
     ( "a", c_int ),
     ( "contenu", POINTER(c_int) )
     ] 
    malib.test_array_retour_complexe.restype = POINTER( ValeurRetour )
    malib.liberer_valeurretour.argtypes = [POINTER( ValeurRetour ),] # on généralise à toutes les valeurs retours 
    class EnveloppeValeurRetourLibDistante():
     def __init__( self ): 
     self.obj_ptr = malib.test_array_retour_complexe( 
     len(tableau) 
     ) 
     self.a = self.obj_ptr.contents.a 
     c = self.obj_ptr.contents.contenu 
     self.contenu = [ c[i] for i in range(0,self.a) ]
     def __del__(self): 
     # l'objet d'enveloppe de l'objet pointeur Python va être supprimé :
     # il déclenche donc la libération de la mémoire allouée par Rust 
     malib.liberer_valeurretour( self.obj_ptr )
    obj_enveloppe = EnveloppeValeurRetourLibDistante()
    print( "[PYTHON] ===>", obj_enveloppe.a, obj_enveloppe.contenu)

    Si la comparaison d'attributs vous est importante, gardez à l'esprit que cTypes créée un nouvel objet interne à chaque appel à l'objet pointé, comme l'illustre l'exemple de la documentation :

    from ctypes import *
    i = c_int(42)
    pi = pointer(i) 
    pi.contents is pi.contents # vaut False 

    Cependant si vous vous ne souciez pas de ce point, pensez au décorateur "property" pour envelopper plus aisément l'appel à l'objet lié par le pointeur, dans l'objet d'enveloppe :

    class EnveloppeValeurRetourLibDistante():
     # (...)
     @property
     def a(self):
     return self.obj_ptr.contents.a 
     @property
     def contenu(self):
     return self.obj_ptr.contents.contenu

    Enfin vous pouvez retrouver très facilement le jeu de poupées imbriquées qui sert à la mécanique interne ("tout est objet en Python") :

     def __init__( self ): 
     self.obj_ptr = malib.test_array_retour_complexe( 
     len(tableau) 
     ) 
     print( self.obj_ptr )
     print( self.obj_ptr.contents )
     print( self.obj_ptr.contents.contenu.contents )

    Au premier niveau, on retrouve bien un objet "pointeur" à la sauce Python :

    <__main__.LP_ValeurRetour object at 0x7fdd12f7f940>

    Qui permet d'accéder via ".contents" à l'objet "reconstitué" via la classe du même nom,

    <__main__.ValeurRetour object at 0x7fdd12f7c6c0>

    Qui permet lui-même d'accéder à mon tableau au travers d'un autre pointeur :

    <__main__.LP_c_int object at 0x7fdd12f7c3c0>

    Qui, devinez quoi ?, me permet d'accéder à ma valeur (la première case qui est à 42, puis de pouvoir d'itérer sur le tableau en mémoire).