CGPointMake & co retournent un objet de la stack ?!
AliGator
Membre, Modérateur
Bonjour à tous
Une petite question existentielle en passant : alors que j'étais en train de créer une structure personnalisée "AxisRange" pour mon programme (oui une struct et pas une classe, en l'occurrence je trouve ça plus adapté) j'ai voulu reprendre un peu le modèle de CGPoint, CGRect & co, avec quelques fonctions basiques genre AxisRangeMake(...), et je suis allé voir vite fait comment c'était foutu à l'intérieur.
Quelle ne fût pas ma surprise en voyant ce code :
A part évidemment si on le crée avec malloc, ou si c'est un type simple genre int, float, ... (en écrivant ceci je me demande si je confond pas avec le C++ où il faut faire attention à ça mais pour les instances de classes créées sur la pile, pour les types C ça s'applique pas ?)
Bref j'ai un gros doute sur le coup. J'ai déjà eu un bug au taf dans un programme C++ où j'ai vu que le gars avait retourné dans son code un objet pourtant alloué sur la pile, donc maintenant je me méfie ^^
A la limite, puisqu'en C99 on peut l'écrire, j'aurais plutôt vu ça moi comme code :
Bref, j'ouvre le débat sur cette problématique de retourner une variable complexe (pas juste un int ou float quoi) pourtant créer sur le tas...
[EDIT]Ces méthodes sont marquées CG_INLINE, autrement dit "static __inline__", est-ce que c'est la réponse à la question ? A savoir que puisque la fonction est static __inline__ du coup le problème de retourner des variables créées sur la pile n'en n'est pas un dans ce cas précis ? (car c'est __inline__ ?)
Une petite question existentielle en passant : alors que j'étais en train de créer une structure personnalisée "AxisRange" pour mon programme (oui une struct et pas une classe, en l'occurrence je trouve ça plus adapté) j'ai voulu reprendre un peu le modèle de CGPoint, CGRect & co, avec quelques fonctions basiques genre AxisRangeMake(...), et je suis allé voir vite fait comment c'était foutu à l'intérieur.
Quelle ne fût pas ma surprise en voyant ce code :
struct CGPoint {<br />Â Â CGFloat x;<br />Â Â CGFloat y;<br />};<br />typedef struct CGPoint CGPoint;<br /><br />...<br /><br />CG_INLINE CGPoint<br />CGPointMake(CGFloat x, CGFloat y)<br />{<br />Â Â CGPoint p; p.x = x; p.y = y; return p;<br />}
Moi ça m'a choqué tout de suite dans CGPointMake : ils créent un objet (une structure) p, ils la modifient... et ils la renvoient ? Autrement dit, ils renvoient une variable créée dans la fonction (sur la pile) ? Il me semblais que cette pratique était à proscrire ? Puisqu'à la sortie de la fonction, la pile est détruite et donc l'objet CGPoint p aussi, non ?A part évidemment si on le crée avec malloc, ou si c'est un type simple genre int, float, ... (en écrivant ceci je me demande si je confond pas avec le C++ où il faut faire attention à ça mais pour les instances de classes créées sur la pile, pour les types C ça s'applique pas ?)
Bref j'ai un gros doute sur le coup. J'ai déjà eu un bug au taf dans un programme C++ où j'ai vu que le gars avait retourné dans son code un objet pourtant alloué sur la pile, donc maintenant je me méfie ^^
A la limite, puisqu'en C99 on peut l'écrire, j'aurais plutôt vu ça moi comme code :
CGPoint CGPointMake(CGFloat x, CGFloat y) { return (CGPoint){x,y}; }
Bref, j'ouvre le débat sur cette problématique de retourner une variable complexe (pas juste un int ou float quoi) pourtant créer sur le tas...
[EDIT]Ces méthodes sont marquées CG_INLINE, autrement dit "static __inline__", est-ce que c'est la réponse à la question ? A savoir que puisque la fonction est static __inline__ du coup le problème de retourner des variables créées sur la pile n'en n'est pas un dans ce cas précis ? (car c'est __inline__ ?)
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
L'erreur à ne pas faire c'est ça:
Les structures sont passées par valeurs, pas par adresse comme pour les tableaux.
Dans ce genre de choses, tout passe par la pile, il n'y a aucune allocation
Et il n'y a aucun pointeur non plus, donc tout passe par copie...
C'est en C++ que ça peut poser des soucis si j'instancie une classe sur la pile et la retourne (surtout si je n'ai pas implémenté l'opérateur de copie de cette classe), non ?
Mais comme les types simples, les structures sont passées par valeurs.
Le problème est identique en C++.
Le constructeur de copie et l'opérateur affectation sont nécessaire si cet objet possède des variables d'instance alloués dynamiquement.
Exemple:
La doc Apple est assez claire sur le sujet des structures retournées par des fonctions.
Dans le cas par exemple d'une architecture intel 32 bits, si la structure dépasse les 8 octets, le compilateur "réinterprète" le prototype de la fonction et tous les appels à cette fonction en utilisant des pointeurs.
Très intéressant, une optimisation automatique en quelque sorte.
Dans les arguments ce n'est sans doute possible de transformer un argument maStruct s; en maStruc * s , que lorsqu'on a mis le qualificateur const.
Dans la valeur de retour, c'est moins clair pour moi. J'imaginais déjà qu'il y avait copie dans la fonction appelante, et cela on ne peut pas y couper sans analyse du code, je ne voyais pourquoi le compilateur s'amuserait à faire une copie intermédiaireÂ
valeur dans la fonction --> valeur renvoyée -> valeur recopiée
En initiation au C, je spécifie les types de "type de base" ou "type simple" pour signifier un mode d'utilisation dans l'affectation, le fait d'être recopié lorsqu'ils passent comme argument dans une fonction, et j'y mets les entiers, les flottants, les pointeurs, les structures, ... , j'exclus les tableaux, les chaà®nes, .... .
Je sais que cela ne tient pas la route très longtemps, mais il faut bien trouver des mots simples pour des débutants. Je n'ai jamais vu de dénomination officielle , même dans K&R. lvalue serait peut-être ce qui s'en rapproche le plus, mais il doit y avoir encore des cas particuliers, alors je préfère ne pas "salir" un mot si cher aux informaticiens.
Maintenant si vous avez mieux à me proposer, je prends ...
...
Mais j'étais sûr que ça lancerait quand même un débat bien intéressant derrière
Type punning isn't funny: Using pointers to recast in C is bad.
Euh, pour un argument, que tu passes en "maStruct*" ou "const maScruct*" ou "maScruct const*" ça fait exactement la même chose... "const" c'est qu'une indication au compilateur pour dire qu'on ne peut modifier soit le pointeur, soit le contenu (ce qu'on peut bypasser par un cast adéquat).
Donc si le compilateur s'autorise à transformer mes arguments structure en argument pointeurs sur structure, il ne faut pas que je change quoique ce soit à la dite structure dans la fonction (sous peine de warning ).
Evidemment si le programmeur ne respecte son engagement le compilateur ne le verra pas forcément ...
Le coup de l'"union" est tout aussi dangereux pour la portabilité...
http://en.wikipedia.org/wiki/Type_punning
Mais pourquoi le compilateur transformerait les arguments "struct" en arguments "struct*" ??
Comment comprends-tu le message de No ?
Si la fonction doit renvoyer une structure de taille >8 ; en interne, c'est l'appelant qui définit la structure de manière opaque et passe le pointeur à la fonction qui la remplit. ???
Je ne vois pas ce que les transformations d'arguments viendrait faire là dedans...
En ça :
Ce qu'on fait en général soi-même pour les grosses structures (quand on n'est pas trop débutant) :P
+1. Hors je n'ai pas souvenir que modifier une structure -ce quelqu'en soit la taille- passée en paramètre ait un impact au niveau de la fonction appellante. Par contre si on passe par un pointeur là oeuf corse...
Oui, mais No et la doc Apple parlent bien de structures retournées par les fonctions.
Pas des structures envoyées aux fonctions en argument.
Je ne vois pas où vous voulez en venir avec vos histoires d'arguments... ???
Autant pour moi, effectivement il est question des paramètres en retour et pas des paramètres en entrée.
ça ok, mais ce que disait No ne me semblait pas aussi clair.
Je voyais plutôt que pour une fonction
... maFunction(const struct Machin unMachin);
il ne serait pas idiot d'optimiser automatiquement pour éviter la copie de la structure unMachin.
Mais enfin... c'est une optimisation laissée à l'utilisateur !
Le compilateur ne peut pas décider comme ça de passer des structures par pointeurs (même const) alors qu'elles sont passées en copies à l'origine
Qu'il fasse une optimisation sans conséquences pour le code de la fonction ET le code de l'appelant, OK... Mais là ce que tu dis, il y aurait des effets de bord de fou
Qu'est-ce qui n'est pas clair ? ???
Ce n'est pas simple que cela.
- Soit il supprime, dans le code de maFunc(), le return uneStructure; et il remplace tous les uneStructure par *res, mais on retrouve alors la même situation que de remplacer des arguments, et que fait-il pour le retour d'un compound littéral : return (maStructure) {... },
- Soit il remplace simplement le return uneStructure; final, par la recopie de cette structure à l'adresse de res. et là On ne voit pas dans ce cas ce qui est gagné. Il y a forcément création d'une structure dans la fonction et recopie à l'adresse voulue.
Comme je disais, on ne s'attend pas à ce que le compilateur soit assez bête pour faire une copie intermédiaire :
Donc pour moi, seule une réinterprétation du code de la fonction pouvait apporter un gain. C'est donc naturellement que je me suis dit l'optimisation dans les arguments est peut-être plus faciles à réaliser si le programmeur s'engage à ne pas les transformer. Parce que là on gagne vraiment. Mais tu as raison, d'une part le programmeur peut le faire lui-même (comme pour maFunc(&res) d'ailleurs), d'autre part la maà®trise des efffets de bord de cette substitution demande une sérieuse réflexion.
Non, c'est vraiment simple.
Si la struct fait moins de 8 octets, elle est transmise entre appelé et appelant par valeur (donc sur 1 ou 2 registres, R3 ou R3+R4 sur PPC, et A ou A+D pour intel).
Au delà des 8 octets, l'adresse de la struct de l'appelant utilisé pour contenir le retour de l'appelé est transmise (il s'agit donc d'un paramètre supplémentaire caché ajouté au proto de l'appelé).
Dans l'appelé, rien ne change.
Si ce n'est que le return struct; final est supprimé et remplacé par une instruction de copie de la struct locale de l'appelé vers l'adresse mémoire passée en paramètre et qui pointe sur la struct de l'appelant.
Mais ça c'est évident, c'est une non-information. Depuis que je fais du C je n'ai jamais imaginé que le compilateur soit assez bête pour faire autrement.
Merci pour la non-information.
Quoique ça cadre bien avec les non-messages que tu as écris plus haut.
Bon dimanche.
Sorry, je parlais pour moi, je suis persuadé que cela a servi à d'autres lecteurs.
Je ne faisais que développer ton sujet vers un questionnement que ton post a suscité, mais manifestement, j'ai du mal exprimé ce questionnement.
Ben c'est pourtant ce qu'il fait pour les types simples et les structures <=8 B, ainsi qu'avec les structures plus grosses sur certains compilateurs.
- Copie de la structure à renvoyer sur la pile d'appel
- Copie à nouveau dans la structure " réceptacle "
Et c'est logique, tant que la donnée est plus petite que la taille d'un pointeur, en empilant un pointeur sur la pile d'arguments à la place, on y perd.