Comment dois-je renvoyer le résultat d’une fonction d’opération binary dans une bibliothèque C?

Je travaille sur une bibliothèque C, dont une partie traite de certains types mathématiques et de leur manipulation. Chaque type a une fonction constructeur / destructeur d’usine qui les alloue et les libère dynamicment. Par exemple:

/* Example type, but illustrates situation very well. */ typdef struct { float x; float y; float z; } Vector3D; /* Constructor */ Vector* Vector3D_new(float x, float y, float z) { Vector3D* vector = (Vector3D*) malloc(sizeof(Vector3D)); /* Initialization code here...*/ return vector; } /* Destructor */ void Vector3D_destroy(Vector3D* vector) { free(vector); } 

Nice & simple, et soulage également les charges d’initialisation appropriée pour un utilisateur.

Maintenant, ma principale préoccupation est de savoir comment gérer les fonctions qui opèrent sur ces types (en particulier comment renvoyer les valeurs de résultat). Presque toutes les opérations binarys aboutiront à une nouvelle instance du même type. Par conséquent, je dois réfléchir à la manière de donner cette Retour à l’utilisateur. Je pourrais simplement renvoyer les choses par valeur , mais il serait préférable de passer des pointeurs, car ils sont plus rapides, compatibles avec les méthodes construct / destructor et ne posent pas autant de problèmes à l’utilisateur.

Actuellement, je l’ai implémenté en ayant des fonctions allouant dynamicment le résultat, puis je lui renvoie un pointeur:

 /* Perform an operation, and dynamically return resultant vector */ Vector3D* addVectors(Vector3D* a, Vector3D* b) { Vector3D* c = Vector3D_new( a->x + b->x, a->y + b->y, a->z + b->z); return c; } 

En renvoyant la valeur directement à l’utilisateur, elle présente l’avantage de pouvoir être chaînée (par exemple, pour être passée directement à une autre fonction en tant que paramètre), telle que:

 /* Given three Vector3D*s : a, b, & c */ float dot = dotProduct(crossProduct(a, addVectors(b, c)); 

Cependant, étant donné la méthode actuelle, cela entraînerait une fuite de mémoire, car le résultat de addVectors() serait transmis directement à crossProduct() , et l’utilisateur n’aurait pas la possibilité de le free() (et la même chose avec crossProduct() qui est passé à dotProduct() ). Pour que cela fonctionne, une personne devrait créer un pointeur pour conserver la ou les valeurs, l’utiliser, puis la free() via ledit pointeur.

 Vector3D* d = addVectors(b, c); Vector3D* e = crossProduct(a, d); float dot = dotProduct(e); Vector3D_destroy(d); Vector3d_destroy(e); 

Cela fonctionne mais est beaucoup moins intuitif et perd l’effet d’enchaînement que je souhaite tant.

Une autre possibilité est que les fonctions d’opération prennent 3 arguments; deux pour les opérandes et un pour stocker le résultat, mais encore une fois pas très intuitif.

Ma question est alors la suivante: quelles sont les méthodes élégantes et productives de travail avec la mémoire dynamic dans les opérations binarys? En prime, une solution utilisée dans une bibliothèque du monde réel serait plutôt cool. Des idées? 🙂

    En plus de la fuite de mémoire que vous avez mentionnée, il existe quelques autres problèmes avec votre système actuel:

    • L’affectation au tas est nettement plus lente que les opérations en stack simple.
    • Chaque allocation devra également être free() d, ce qui signifie que chaque instance nécessitera au moins 2 invocations de fonctions, alors qu’utiliser une conception basée sur une stack n’en exigerait aucune.
    • Comme la mémoire doit être gérée manuellement, cela laisse beaucoup plus de place aux pertes de mémoire.
    • Les allocations de mémoire peuvent échouer! Un système basé sur une stack atténuerait cela.
    • L’utilisation de pointeurs nécessiterait un déréférencement. Ceci est plus lent que l’access direct et nécessite plus de syntaxe (peut-être négligée).

    De plus, de nombreux compilateurs mettent en cache la mémoire utilisée pour la stack d’un programme et peuvent apporter des améliorations significatives par rapport au tas (qui n’est presque jamais mis en cache (si possible!)).

    En bref, il est préférable de s’appuyer sur la stack pour tout, non seulement pour les performances, mais également pour la maintenance et le code propre. La seule chose à retenir est que la stack est finie et qu’il pourrait également être facile de devenir fou. Utilisez la stack pour les données à court terme (un résultat d’opération binary dans ce cas) et le segment de mémoire pour les données à long terme plus lourdes.

    J’espère que ça aide! 🙂

    Remarque: Une grande partie de l’information contenue dans cette réponse provient de @ Justin .

    L’atsortingbution à l’intérieur de l’opérateur n’est pas aussi pratique que cela puisse paraître.
    Cela est principalement dû au fait que vous ne disposez pas de récupération de place, mais également au fait que vous devez vous soucier des allocations échouées.

    Considérons ce code:

     Vector3D *v1,*v2,*v3; Vector3d v4 = addVectors(v1,multiplyVectors(v2,v3)); 

    Ça a l’air bien.
    Mais que se passe-t-il avec le vecteur renvoyé par multiplyVectors ? Une fuite de mémoire.
    Et que se passe-t-il si l’atsortingbution échoue? Un crash dans une autre fonction.

    J’irais pour une addition sur place:
    void addVectors(Vector3D *target, const Vector3D *src);
    Ceci est équivalent à target += src; .

    Je ferais aussi simple que

     Vector3D addVectors(Vector3D a, Vector3D b) { Vector3D c; cx = ax + bx; cy = ay + by; cz = az + bz; return c; } 

    Si l’appelant en a vraiment besoin sur le tas, il peut le copier lui-même.