CGPointMake & co retournent un objet de la stack ?!

AliGatorAliGator Membre, Modérateur
mars 2009 modifié dans API AppKit #1
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 :
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__ ?)
«1

Réponses

  • Philippe49Philippe49 Membre
    06:51 modifié #2
    Pas de problème en C. Avec point=CGPointMake( , ); le "p" renvoyé est copié dans point, les structures sont des types simples et l'affectation est une recopie.
  • mpergandmpergand Membre
    06:51 modifié #3
    Je confirme, c'est une copie comme dans NSRect r2=r1;

    L'erreur à  ne pas faire c'est ça:
    <br />CGPoint* CGPointMake(CGFloat x, CGFloat y)<br />{<br />&nbsp; &nbsp; CGPoint p; p.x = x; p.y = y; <br /><br />&nbsp; &nbsp;  return &amp;p;&nbsp; // !!!!!<br />}
    


    Les structures sont passées par valeurs, pas par adresse comme pour les tableaux.
    <br />NSRect foo(NSRect rect)&nbsp; // 1ere copie de rect dans la pile de foo<br />{<br />&nbsp; return rect; // 2eme copie de rect dans la pile de la fonction appelante<br />}
    

  • CéroceCéroce Membre, Modérateur
    06:51 modifié #4
    Ali, faut que tu arrêtes de poster à  3h35 !
  • schlumschlum Membre
    06:51 modifié #5
    Et de fumer la moquette pour se décompresser  :fouf):
    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...
  • AliGatorAliGator Membre, Modérateur
    06:51 modifié #6
    Ok donc en fait c'est parce que c'est considéré comme un "type simple" et donc qu'il y a un recopie qui est effectuée quoi. Comme si je retournais un int ou un bool quoi. Donc pas de soucis.

    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 ?
  • mpergandmpergand Membre
    mars 2009 modifié #7
    J'appelle pas les structures des types simples  ::)

    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:
    <br />class MaClasse		<br />	{<br />	public:<br />			<br />		int	length;		// taille data<br />		char*	data;<br />				 <br />		MaClasse() : length(0), data(NULL) {}<br />						&nbsp; <br />		MaClasse(Maclasse&amp; obj)<br />			{<br />			*this=obj;		// évite d&#39;écrire deux fois la même chose (affectation)<br />			}<br />							<br />		MaClasse&amp; operator=(const MaClasse&amp; obj)<br />			{<br />			length=obj.length;<br />			data=new char[length];<br />			std::memcpy(data,obj.data,length);<br />				<br />			return *this;		<br />			}	<br />							<br />							<br />		 virtual ~MaClasse() <br />			{ <br />			if(data!=NULL) <br />				delete data; <br />			}<br />							<br />		};	<br />
    

  • schlumschlum Membre
    06:51 modifié #8
    En C++, si tu ne définis pas le constructeur de recopie, ça va fonctionner à  la compilation (recopie par défaut...) mais ça plantera à  l'exécution si le constructeur fait de l'allocation et le destructeur de la désallocation.
  • NoNo Membre
    06:51 modifié #9
    dans 1236414263:

    Pas de problème en C. Avec point=CGPointMake( , ); le "p" renvoyé est copié dans point, les structures sont des types simples et l'affectation est une recopie.

    dans 1236416089:

    Je confirme, c'est une copie comme dans NSRect r2=r1;

    dans 1236430210:

    Et de fumer la moquette pour se décompresser  :fouf):
    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...


    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.

  • Philippe49Philippe49 Membre
    mars 2009 modifié #10
    dans 1236446654:

    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


    dans 1236443117:

    J'appelle pas les structures des types simples  ::)

    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 ...

  • AliGatorAliGator Membre, Modérateur
    06:51 modifié #11
    Héhé c'est bizarre mais d'un côté je savais que c'était une question conne, genre je me doutais que c'était pas un pb vu qu'Apple l'utilisait dans une méthode si "bas niveau" / utilisée par tout le framework, qqun l'aurait remarqué avant sinon quand même...

    ...

    Mais j'étais sûr que ça lancerait quand même un débat bien intéressant derrière :D
  • Philippe49Philippe49 Membre
    mars 2009 modifié #12
    Ah ça y est j'ai retrouvé ce lien (que je n'avais eu le temps de lire) sur ce sujet ... je le lis parce que le titre est provoquant, et qu'à  priori je n'y crois pas.

    Type punning isn't funny: Using pointers to recast in C is bad.
  • schlumschlum Membre
    06:51 modifié #13
    dans 1236449512:

    dans 1236446654:

    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


    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).
  • Philippe49Philippe49 Membre
    mars 2009 modifié #14
    Ah ben si cela change quelque chose. parce que si on passe le pointeur en argument, et qu'on change quelque chose sur la structure dans la fonction, cela va se reporter sur la variable dans la fonction appelante.
    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 ...
  • schlumschlum Membre
    06:51 modifié #15
    dans 1236453286:

    Ah ça y est j'ai retrouvé ce lien (que je n'avais eu le temps de lire) sur ce sujet ... je le lis parce que le titre est provoquant, et qu'à  priori je n'y crois pas.

    Type punning isn't funny: Using pointers to recast in C is bad.


    Le coup de l'"union" est tout aussi dangereux pour la portabilité...
    http://en.wikipedia.org/wiki/Type_punning
  • schlumschlum Membre
    06:51 modifié #16
    dans 1236454370:
    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  >:) ).


    Mais pourquoi le compilateur transformerait les arguments "struct" en arguments "struct*" ??
  • Philippe49Philippe49 Membre
    06:51 modifié #17
    dans 1236455935:

    Mais pourquoi le compilateur transformerait les arguments "struct" en arguments "struct*" ??


    Comment comprends-tu le message de No ?

    dans 1236446654:

    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.
  • schlumschlum Membre
    06:51 modifié #18
    Ben comme ce que c'est :
    The method used by GCC is as follows: a structure or union which is 1, 2, 4 or 8 bytes long is returned like a scalar. A structure or union with any other size is stored into an address supplied by the caller (usually in a special, fixed register, but on some machines it is passed on the stack). The target hook TARGET_STRUCT_VALUE_RTX tells GCC where to pass this address.
    


    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...
  • schlumschlum Membre
    06:51 modifié #19
    En gros, ça transforme ça :

    struct maStruct maFunc();<br /><br />struct maStruct res = maFunc();
    


    En ça :
    void maFunc(struct *maStruct);<br /><br />struct maStruct res;<br />maFunc(&amp;res);
    


    Ce qu'on fait en général soi-même pour les grosses structures (quand on n'est pas trop débutant) :P
  • MalaMala Membre, Modérateur
    06:51 modifié #20
    dans 1236454370:

    Ah ben si cela change quelque chose. parce que si on passe le pointeur en argument, et qu'on change quelque chose sur la structure dans la fonction, cela va se reporter sur la variable dans la fonction appelante.

    +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...


  • schlumschlum Membre
    06:51 modifié #21
    dans 1236467119:

    dans 1236454370:

    Ah ben si cela change quelque chose. parce que si on passe le pointeur en argument, et qu'on change quelque chose sur la structure dans la fonction, cela va se reporter sur la variable dans la fonction appelante.


    +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...  ???
  • MalaMala Membre, Modérateur
    mars 2009 modifié #22
    dans 1236467809:

    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.
  • Philippe49Philippe49 Membre
    06:51 modifié #23
    dans 1236466511:

    En gros, ça transforme ça :

    struct maStruct maFunc();<br /><br />struct maStruct res = maFunc();
    


    En ça :
    void maFunc(struct *maStruct);<br /><br />struct maStruct res;<br />maFunc(&amp;res);
    


    Ce qu'on fait en général soi-même pour les grosses structures (quand on n'est pas trop débutant) :P


    ç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.


  • schlumschlum Membre
    mars 2009 modifié #24
    dans 1236469440:

    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  :o
  • schlumschlum Membre
    06:51 modifié #25
    La doc Apple est assez claire sur le sujet des structures retournées par des fonctions.


    Qu'est-ce qui n'est pas clair ?  ???
  • Philippe49Philippe49 Membre
    mars 2009 modifié #26
    dans 1236470390:

    La doc Apple est assez claire sur le sujet des structures retournées par des fonctions.

    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 :
    dans 1236449512:

    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


    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.

  • NoNo Membre
    mars 2009 modifié #27
    dans 1236506393:

    dans 1236470390:

    La doc Apple est assez claire sur le sujet des structures retournées par des fonctions.

    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.


    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.
  • Philippe49Philippe49 Membre
    06:51 modifié #28
    dans 1236510332:

    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.
  • NoNo Membre
    06:51 modifié #29
    dans 1236510883:

    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.
  • Philippe49Philippe49 Membre
    06:51 modifié #30
    dans 1236511190:

    Merci pour la non-information.

    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.
  • schlumschlum Membre
    06:51 modifié #31
    dans 1236510883:

    dans 1236510332:

    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.


    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.
Connectez-vous ou Inscrivez-vous pour répondre.