Avec g++-10 je n'ai pas de soucis avec le memcpy, cette fonction est bête et ne fait que de copier les données se trouvant dans les void* d'un delegate à l'autre, je n'ai besoin de ne faire qu'une bêtes copie d'octets, rien d'autre, ce n'est pas le delegate qui se charge de cloner les objets qu'il contient.
Tu n'es pas capable de montrer un code minimaliste qui reproduit l'erreur.
Donc c'est que tu n'as pas compris l'erreur.
Donc c'est que l'erreur ne vient pas de map/vector mais de ton code.
J'avais mit un code source qui plantait sur mingw avec une std::map, je vais le tester sur g++ sous linux, je l'avais mit sur un autre sujet mais je vais le remettre dès que j'ai fini.
EDIT : bug de mingw, avec g++ sous linux ça ne plante pas.
Faut il rappeler mingw n'est que la version de Gcc (G++, ici) capable de fonctionner sous windows.
S'il y a -- effectivement -- fallu faire quelques adaptations pour passer d'un système POSIX au système windows, tu peux malgré tout te dire que l'ensemble du code reste malgré tout parfaitement identique
Donc, si ton code plante avec MinGW mais ne plante pas avec G++ sous linux, au lieu d'aller penser à un bug du compilateur, tu ferais sans doute bien de revoir l'ensemble du code que tu as écrit, car c'est très certainement là que se trouve l'origine de ton problème.
OmbreNoire a écrit:
Avec g++-10 je n'ai pas de soucis avec le memcpy, cette fonction est bête et ne fait que de copier les données se trouvant dans les void* d'un delegate à l'autre, je n'ai besoin de ne faire qu'une bêtes copie d'octets, rien d'autre, ce n'est pas le delegate qui se charge de cloner les objets qu'il contient.
Mais te rend tu seulement compte des aberrations que tu peux dire?
Le problème ne vient absolument pas du fait que ce soit le délégate qui s'occupe de se cloner ou non (même si ce serait sans doute encore pire si c'était le cas),
Le problème vient du fait que, à partir du moment où tu as un void *, tu n'as plus aucun moyen de savoir ce qui se trouve effectivement à l'adresse représentée par ce pointeur: y trouve-t-on un entier (mais de quel type, alors?), un réel (dont il faudra aussi connaitre le type)? ou le début d'une structure plus complexe?
Rien que cela nous pose déjà un premier problème: combien d'octets va-t-on devoir copier?
Ben oui, pour les types entiers (qu'ils soient signés ou non signés), on peut partir du principe que ce sera
1 si on a affaire à un char "seul"
2 si on a affaire à un short "seul"
4 si on a affaire à un int "seul"
4 ou 8 si on a affaire à un long "seul" (en fonction de l'implémentation et de l'architecture d'exécution)
8 si on a affaire à un long long "seul"
4 si on a affaire à un float "seul"
8 si on a affaire à un double "seul"
16 si on a affaire à un long double "seul"
la somme des tailles de toutes les données qui composent la structure et des éventuels espaces placés par le compilateur pour assurer l'alignement correct des données (pour s'assurer que chaque donnée soit accessible au travers d'une adresse unique)
Déjà rien que comme cela, on se rend compte qu'il nous manque "une information majeure" pour la copie des données; information qui posera d'autant plus de problème si, pour notre malheur, il s'agit de copier un ensemble de données contigu en mémoire.
Mais, pensons positif, mettons que l'on arrive -- dieu seul sait comment -- à s'assurer de la copie du nombre correct d'octets pour les types primitifs et pour les éventuels ensembles de données (de type primitifs) contigus en mémoire.
Le problème devient encore plus compliqué à résoudre lorsqu'il s'agit de structures "complexes".
Car on peut -- en gros -- diviser ces structures en deux catégories:
Les structures qui ne contiennent que des données "automatiques" (pour lesquelles nous ne faisons pas appel à l'allocation dynamique de la mémoire) et que l'on qualifie généralement de POD (Plain Old Data) comme
struct RgbColor{
int red;
int green;
int blue;
};
ou
struct vec3{
float x;
float y;
float z;
};
Et les structures "non POD", comme les structures de données des différentes collections dynamique de la STL, et qui vont ressembler à quelque chose comme
template< typeame T /* pas le courage de copier précisément */>
struct vector{
size_t capacity; // le nombre d'éléments au dela duquel il
// faudra augmenter l'espace mémoire disponible
size_t size; // le nombre actuel d'éléments dans le tableau
T * pointer; // un pointeur sur le premier éléments d'un espace mémoire
// suffisant que pour représenter l'ensemble des
// capacity elements attendus (avant augmentation
// de l'espace mémoire)
};
ou comme
template <typename T, /* toujours la flegme */>
struct list{
size_t size; // le nombre d'éléments
ListNode * first; // un pointeur sur le premier élément de la liste
ListNode * last; // un pointeur sur le dernier élément de la liste
};
Et ce (ces) pointeur(s) vont forcément foutre le bordel. Car, même si tu sais qu'il s'agit de pointeur, memcpy va faire ce pour quoi elle a été concue: copier le contenu d'une adresse mémoire vers une autre adresse mémoire.
Sauf que, dans ce cas précis, la donnée représentée par pointer, first ou last représente ... une adresse mémoire. Et c'est donc la valeur de cette adresse mémoire qui sera copie d'une place à une autre.
OUCHHH... Cela signifie que l'on va se retrouver avec deux delegate (l'"original" et la "copie") qui utiliseront les même adresse mémoire, et donc, que quand l'un sera détruit, il voudra ** forcément ** libérer la mémoire qu'il croit lui appartenir.
Le premier à être détruit va donc "remporter la palme" car il pourra effectivement libérer la mémoire, le deuxième essayera de libérer une mémoire qui ... a déjà été libérée, et ca va planter.
Si bien que, pour résoudre ce problème, il faudrait (à condition d'arriver à se rendre compte que l'on a affaire à un pointeur) refaire l'ensemble du processus d'allocation dynamique de la mémoire (à condition de savoir quelle taille doit être allouée, bien sur) avant de pouvoir copier ce qui se trouve effectivement à l'adresse mémoire indiquée au niveau de "l'original" vers la mémoire nouvellement allouée de "la copie".
Et ca, c'est dans le cas le plus simple. Car, le problème de la liste, par exemple, c'est que la structure ListNode va ressembler à quelque chose comme
et qu'il faudra donc s'assurer que l'adresse représentée respectivement par les pointeurs previous et next représentent bel et bien les adresses d'éléments qui appartiennent bel et bien soit à l'original, soit à la copie. On ne peut donc -- de nouveau -- pas se contenter d'un simple memcpy dans ce cas
Note bien que j'ai pris deux exemples parmi les plus simple en me focalisant sur std::vector et sur std::list et que le problème n'est en tout cas certainement pas plus simple à résoudre pour ce qui est des arbres binaires (std::set et std::map) et autres joyeusetés de ce genre.
Mais tant que tu n'auras pas compris que, si un de tes essais plante, c'est du coté de ton propre code que tu dois tourner le regard, nous ne pourrons décidément rien faire pour t'aider.
Et tant que tu n'auras pas compris qu'il est parfois nécessaire de ravaler sa fierté et d'admettre ses erreurs si on veut pouvoir évoluer, tu continueras à recevoir des tartes dans la gueule, surtout si tu nous tend la perche pour te battre.
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Mais vraisemblablement dans des milliards de cycles CPU, dans du code qui n'a rien à voir avec la choucroute.
Bon débogage.
Des trucs qui font du "polymorphisme" de fonction de callback, il en existe des centaines, alors pourquoi réinventer une roue carrée (pour un besoin qui ne me semble toujours pas justifier) ?
Avant de faire une usine à gaz à base d'UB, pensez à voir comment votre engin est sensé s'intégrer dans un workflow de création de jeu.
Un game designer n'ouvrira jamais un fichier de code source C++ pour tester une idée de gameplay (ni même moi d’ailleurs), un fichier de paramétrage oui. Donc vos constexp à tire-larigot, c'est nimpornawak.
Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
Alors pour le premier problème, la taille des données, je la connais à la création du delegate, j'ai juste besoin de la stocker pour la copie, je fais un sizeof sur le type, c'est la somme des tailles de toutes les données (le pointeur sur fonction ainsi que la taille de la liste des arguments du delegate, si je ne me trompe pas à propos de ce que fais l'opérateur sizeof) :
return Data {
delegate,
storage_deleter,
&late_params_t::deleter,
new storage_t{
static_cast<DynamicFunction<R(ToStore_t<Args>...)>&&>(f),
typename storage_t::TupleArgs{static_cast<Args&&>(args)...}
},
nullptr,
sizeof(storage_t),
0
};
Par contre je ne sais pas si sizeof tient compte de l'alignement lorsqu'il calcule la taille que les données occupent en mémoire, le soucis vient sûrement de là.
Et les pointeurs, je n'ai pas besoin de les réallouer, puisque ce n'est pas le delegate qui les libères, lui, il delete juste le tuple de la liste des arguments du delegate, mais pas les arguments eux même.
Donc ici si storage contient un pointeur, il n'est pas delete ici, sinon, ça poserait problème même si on ne fait pas de copie du delegate :
data.storage_deleter(data.storage);
donc, ce n'est pas grave si deux delegates, ont le même pointeur dans storage, par contre je dois réallouer et copier comme ça, lorsque la liste des arguments du delegate est libérée, elle ne l'est pas pour la copie du delegate.
char* tab1 = new char[rhs.data.storage_size];
memcpy(tab1, rhs.data.storage, rhs.data.storage_size);
Ne vous inquiéter pas, je sais ce que je fais!!! Mais apparemment vous ne comprenez pas ce que je veux faire. :/
Sinon c'est le but de mon framework de faire de la métaprog et d'utiliser au maximum les fonctionnalité du c++ moderne, sinon, en ce qui concerne le polymoprhisme pour la fonction de callback, je dois encore faire des tests en compilation sur les types.
Mais au pire j'ai l'implémentation qui utilise l'héritage, qui elle, est moins galère à implémenter surtout pour la copie.
Dans le code suivant, que se passe-t-il si la donnée originale (qui a servi a remplir data.storage et data.storage_size) est un std::vector, et qu'il a changé de taille entre-temps ?
Dans le code suivant, que se passe-t-il si la donnée originale (qui a servi a remplir data.storage et data.storage_size) est un std::vector, et qu'il a changé de taille entre-temps ?
Tout dépend si tu passes une référence ou un pointeur sur le std::vector ou si tu fais un passage par valeur lors du passage de paramètres au delegate.
Sais tu, au moins, quelle est la valeur renvoyée par sizeof pour fvoid si fvoid est déclaré sous la forme de
using fvoid = std::function<void()>;
et pour fint qui prendrait la forme de
using fint = std::function<int(int)>;
ou encore pour ffloat qui prendrait la forme de
using ffloat = std::function<float(float, float)>;
voire, pourquoi pas, pour fdouble qui prendrait la forme de
using fdouble = std::function<double(double, double, double)>;
Sais tu quelle est la taille renvoyé par ce qui sera l'appel à des fonctions équivalente?
Sais tu quelle est la taille renvoyée par ce qui sera l'appel (au travers d'expression lambda) à des fonctions membres non statiques équivalentes?
Et bien, figure toi que je me suis posé la question. J'ai donc fait ce que tout bon développeur ferait dans cette circonstance: j'ai écrit un test minimal qui me fournisse les réponses que je cherche. Et devine le résultat??? Allez, je te le donne en direct ...
Ben, en gros: un alias sur std::function aura toujours la même taille, quel que soit le type de retour, le nombre ou le type des paramètres attendus par la fonction: 32 bytes.
Et toutes les expressions lambda, quoi qu'elle fasse, quel que soit leur type de retour ou le nombre d'argument qu'elles vont prendre, auront également la même taille: 8 bytes.
Je pourrais perdre une demi heure ou une heure à tenter de t'expliquer pourquoi c'est normal, mais je suis sur que quelqu'un d'aussi compétent que toi n'en a rien à foutre, parce qu'il le sait déjà, n'est-ce pas?
OmbreNoire a écrit:
Alors pour le premier problème, la taille des données, je la connais à la création du delegate, j'ai juste besoin de la stocker pour la copie, je fais un sizeof sur le type, c'est la somme des tailles de toutes les données (le pointeur sur fonction ainsi que la taille de la liste des arguments du delegate,
Je vois quand même juste un petit problème avec ton raisonnement...
Car mon petit test rapide vient de démontrer que l'on se fout en réalité royalement de la taille ou du nombre des paramètres lors de l'appel d'une fonction: tout ce dont on a besoin, c'est l'adresse à laquelle cette fonction va commencer (le pointeur de fonction)
De plus, s'il est très bien de connaitre la taille des paramètres qui devront être copiés, cela ne résout pas le problème de base: si le paramètre est une structure complexe, tu n'a aucun moyen de savoir si, parmi toutes les données "plus simples" qui la composent, il n'y a pas une (ou plusieurs) de ces données qui serait éventuellement un pointeur vers un espace mémoire alloué de manière dynamique.
Parce que, encore une fois, les données "automatiques", celles pour lesquelles nous n'aurions pas eu recours à l'allocation dynamique de la mémoire, pourraient sans problèmes être copiées à coup de memcpy, mais il reste nécessaire de faire une copie en profondeur (avec allocation dynamique de mémoire et tout ce qui s'en suit) dés que l'on a affaire à un pointeur.
Et, en fait, c'est même pas forcément vrai, car si le pointeur représente l'adresse mémoire d'une donnée automatique, nous n'avons pas besoin d'effectuer une allocation dynamique, mais, par contre nous devons nous assurer qu'il pointe effectivement vers la donnée automatique propre à l'instance du paramètre utilisé
Donc, quoi qu'il arrive, dés que tu te retrouve avec un pointeur qui n'est pas pointeur de fonction, tu es dans la merde si tu essaye "simplement" d'utiliser memcpy dessus.
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Ok de toute façon cette implémentation avec des void* et les memcpy pose problème.
J'ai des erreurs en compilation maintenant il copie une valeur et pas une référence apparemment et lorsque je crée le delegate dans l'appel de la fonction ça plante, il appelle le destructeur avant la copie. Je n'ai pas de soucis avec l'implémentation utilisant l'héritage à part pour les objets non copiable car il compile tout, même les structure template non utilisées lorsque je passe un type, et comme la structure val contient soit un pointeur ou une valeur, le compilateur râle car l'objet est non copiable, pour un std::unique_ptr par exemple.
Bref c'est pas évident il faut gérer tout les cas.
Non, c'est pas "maintenant". Tu as toujours eu un code moisi. C'est pour ca que tu as des bugs.
Et tu le sais très bien, c'est pour ca que tu ne veux pas montrer un code minimal qui reproduit le problème. Tu sais que tu te mens a toi même et que tu ne veux juste pas admettre que tu as a tort.
Non, c'est pas "maintenant". Tu as toujours eu un code moisi. C'est pour ca que tu as des bugs.
Et tu le sais très bien, c'est pour ca que tu ne veux pas montrer un code minimal qui reproduit le problème. Tu sais que tu te mens a toi même et que tu ne veux juste pas admettre que tu as a tort.
Non je cherche juste des réponses pour voir si je n'ai pas fait quelque chose d'illogique.
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Discord NaN. Mon site.
Discord NaN. Mon site.
Si vous ne trouvez plus rien, cherchez autre chose.
et pour fint qui prendrait la forme de ou encore pour ffloat qui prendrait la forme de voire, pourquoi pas, pour fdouble qui prendrait la forme de
Discord NaN. Mon site.
Discord NaN. Mon site.