Les Closures en C (et donc Objective-C)

2»

Réponses

  • AliGatorAliGator Membre, Modérateur
    23:53 modifié #32
    En fait perso je prend toujours l'habitude de faire un .h séparé pour les prototypes. Du coup je met la déclaration dans le .h et dans ce cas j'aurais mis le main() au début sans doute... ou à  la toute fin, mais j'aurais de toute façon mis les prototypes avant donc (enfin via un #include quoi)

    Par contre, quand je ne fais pas de header, et surtout particulièrement quand je fais un code juste vite fait pour tester un truc en C qui  n'a pas vocation de durer mais juste de tester qqch, et que y'a que une ou 2 fonctions comme ici un main et une fonction secondaire, je ne m'embête pas à  faire la déclaration avant et définition ensuite, je fais comme j'ai fait dans l'exemple.
    Mais c'est juste pour "pas me prendre la tête" d'autant qu'il m'arrive alors d'adapter le proto de la fonction (type et nombre d'arguments, ...) en cours de route, puisque c'est un "test vite fait" sans trop de réflexion d'architecture avant, et que déclarer avant et définir après m'obligerait à  modifier le proto à  2 endroits. Quand c'est pour faire un truc vite fait en 2mn, je suis pas super formaliste non plus quoi.


    Mais sinon bien sûr dans l'ensemble, déclarer avant de définir c'est évidemment la bonne chose à  faire.



    Sinon pour les autres questions, en tant que pro je ne compte pas l'utiliser tout de suite tout de suite, car :
    • je ne programme plus trop pour MacOS, surtout pour iPhone maintenant, et les API Apple du SDK iPhone n'utilisent pas les blocks
    • c'est encore trop nouveau pour que j'en fasse vraiment usage en dev pro, surtout quand le code doit être repris ou travaillé aussi par des collègues qui ne les connaissent pas
    • en plus comme toi j'aimerai bien savoir si c'est assez standardisé (compatible avec les autres compilateurs standards UNIX ou que dispo pour l'instant dans le gcc d'Apple, ...) pour ne pas faire du code trop spécifique quand j'utilise du C pur à  vocation portable.


    Mais dans des cas perso où l'utilité se verrait évidente, genre éviter des callbacks alambiquées et préférer les blocks, ce genre de chose, je pense m'y mettre rapidement (aussi pour prendre l'habitude et le pli) c'est clair, car c'est vraiment prometteur et utile et sympa :)

    Et en tout cas dans mon environnement pro, à  l'occasion (surtout si je vois un projet où ça serait vraiment utile et vaudrait le coup), je n'hésiterais pas à  faire partager à  mes collègues l'existence de ces blocks pour qu'ils sachent que ça existe et soient tentés de les utiliser aussi. Parce que c'est comme ça aussi qu'on incite les gens à  utiliser les nouveautés aussi.

    Je présume juste que ça mettra un peu de temps à  s'imposer, le temps que toutes les plateformes aient en standard d'installé un compilateur C supportant les blocs (s'il faut installer une version alternative du compilateur par défaut installé par l'OS, ça va en rebuter plus d'un de faire la manip et l'installation "juste pour supporter les blocks", c'est sûr) et que ça rentre dans les moeurs, mais faut introduire ça à  nos collègues petit à  petit (d'abord en extra / solution alternative, avant d'en faire usage "à  foison") quoi.[/list]
  • Philippe49Philippe49 Membre
    septembre 2009 modifié #33
    dans 1252835985:


    Exemple : "Pourquoi ce code ne marche pas ?":
    <br />void testNumber(int g, int val)<br />{<br />&nbsp; &nbsp; void (^blk)(int);<br /><br />&nbsp; &nbsp; if(g == 0)<br />&nbsp; &nbsp; &nbsp; &nbsp; blk = ^(int i) { if(i &gt; 10) printf(&quot;The number is correct.&quot;); };<br />&nbsp; &nbsp; else if(g == 1)<br />&nbsp; &nbsp; &nbsp; &nbsp; blk = ^(int i) { if(i &lt; 10) printf(&quot;The number is correct.&quot;); };<br /><br />&nbsp; &nbsp; blk(val);<br />} <br />
    



    Je vois à  la place de la ligne blk(val);
    if(blk) blk(val); else puts(&quot;le block n&#39;est pas défini&quot;);
    


    et
    &nbsp; &nbsp; void (^blk)(int)=NULL;
    

    ?
  • psychoh13psychoh13 Mothership Developer Membre
    23:53 modifié #34
    Oups je m'excuse, je vais corriger un peu le code pour montrer le vrai problème
    <br />void testNumber(int g, int val)<br />{<br />&nbsp; &nbsp; void (^blk)(int);<br /><br />&nbsp; &nbsp; if(g == 0)<br />&nbsp; &nbsp; &nbsp; &nbsp; blk = ^(int i) { if(i &gt; 10) printf(&quot;The number is correct.&quot;); };<br />&nbsp; &nbsp; else<br />&nbsp; &nbsp; &nbsp; &nbsp; blk = ^(int i) { if(i &lt; 10) printf(&quot;The number is correct.&quot;); };<br /><br />&nbsp; &nbsp; blk(val);<br />}
    

    Quelque soit "g", ce code ne marchera pas... Pourquoi ?

    Au passage, le C n'attribut aucune valeur par défaut donc le if(blk) ne peut pas marcher quoiqu'il arrive, il faudrait pour ça écrire :
    void (^blk)(int) = NULL;
    


    Mais même avec le test ça ne marchera pas.
  • Philippe49Philippe49 Membre
    septembre 2009 modifié #35
    A l'essai cela marche, .. ce qui ne veut pas dire que le code soit correct.
    Là  je sèche , le côté local du block , le fait que le block ne pourrait être défini qu'à  la compilation ?
    et les résultats que j'obtiens pour
    testNumber(2,8) ;<br />	testNumber(0,12);<br />	testNumber(0,8);<br />	testNumber(1,12);<br />	testNumber(1,8);<br />	testNumber(0,8);<br />	testNumber(2,8);
    


    qui montre clairement que les blocs sont recréés à  la même adresse ne serait pas un hasard de choix d'adresse à  l'exécution mais un choix précis à  la compilation  ?
    <br />% pgm<br />0x100000d67&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // adresse de la fonction<br />0x0&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // ensuite adresse du block blk<br />le block n&#39;est pas défini<br />0x100001080<br />g=0 , i&gt;10<br />0x100001080<br />g=0 , i≤10<br />0x1000010a0<br />g=1 , i≥10<br />0x1000010a0<br />g=1, i&lt;10<br />0x100001080<br />g=0 , i≤10<br />0x0<br />le block n&#39;est pas défini<br />% <br />
    



    Oui j'avais rajouté void (^blk)(int) = NULL; en edit, avec une petite frayeur tout de même sur la nature "pointeur" d'un block.


    [EDIT] le code d'essai
    <br />#include &lt;stdio.h&gt;<br /><br />void testNumber(int g, int val);<br />int main (void) {<br />	printf(&quot;%p&#092;n&quot;,testNumber);<br />	testNumber(2,8) ;<br />	testNumber(0,12);<br />	testNumber(0,8);<br />	testNumber(1,12);<br />	testNumber(1,8);<br />	testNumber(0,8);<br />	testNumber(2,8);	<br />}<br />void testNumber(int g, int val)<br />{<br />	void (^blk)(int)=NULL;<br /><br />	if(g == 0)<br />		blk = ^(int i) { if(i &gt; 10) puts(&quot;g=0 , i&gt;10&quot;); else puts(&quot;g=0 , i≤10&quot;);};<br />	else if(g == 1)<br />		blk = ^(int i) { if(i &lt; 10) puts(&quot;g=1, i&lt;10&quot;); else puts(&quot;g=1 , i≥10&quot;);};<br />	<br />	printf(&quot;%p&#092;n&quot;,(void*)blk);<br />	if(blk) blk(val); else puts(&quot;le block n&#39;est pas défini&quot;);<br />} <br />
    
  • psychoh13psychoh13 Mothership Developer Membre
    23:53 modifié #36
    Le truc c'est que tu as de la chance en l'occurrence, il s'agit d'un undefined behavior en l'occurrence.

    Il faut savoir que les blocks sont les premiers objets ObjC alloués sur la pile, ça veut dire qu, tant qu'ils ne sont pas copiés sur le tas, ils disparaà®tront lorsqu'on sort de la portée dans laquelle ils sont définis...

    En l'occurrence, le block est créé dans un if() puisque écrire :
    if(condition) instruction;
    


    équivaut à  écrire:
    if(condition)<br />{<br />&nbsp; &nbsp; instruction;<br />}
    


    Donc la portée du block est limitée au if ! C'est-à -dire que quand tu passes à  l'instruction suivante le block n'existe théoriquement plus... En l'occurrence pourquoi il existe toujours bah tout simplement parce que la pile n'a pas été écrasée lors de l'appel de ton block...
  • AliGatorAliGator Membre, Modérateur
    septembre 2009 modifié #37
    J'allais proposer d'utiliser le mot clé "__block" sur ta variable blk, mais en fait si j'ai bien compris, à  la base ce "__block" c'est plutôt pour déclarer que des variables peuvent être accessibles en écriture par le code des blocks déclarés dans le même scope, de sorte que ces variables puissent être modifiées par le block (car passées en référence et non plus par copie, en qques sortes). Bon, je me demande s'il ne peut pas jouer un rôle dans ce cas aussi, mais c'est pas gagné...

    En tout cas une petite visite dans la doc Apple peut valoir le coup : Introducing Blocs and GCD " Blocks Programming Topics
    En particulier pour ton problème, ils exposent justement ce cas particulier à  éviter de par le scope des blocks qui est restreint au scope entre { et }, comme le mentionne psychoh13 : Doc Apple : Block Literals. Comme c'est "stack-local", c'est créé dans le contexte du bloc de code entre { } et donc détruit quand tu sors de ce bloc de code.
    Certes ta variable blk est déclarée hors de ce bloc de code, mais le block que tu lui affectes est créé à  la volée dans le if... et donc détruit à  la volée aussi quand tu sors du if !



    La solution est peut-être si tu veux vraiment garder ce cas particulier, d'utiliser [tt]Block_copy()[/tt] et [tt]Block_destroy()[/tt] pour garder le block en mémoire hors du scope du if ? A tester en tout cas !
  • psychoh13psychoh13 Mothership Developer Membre
    23:53 modifié #38
    Le problème n'est pas la variable "blk" c'est la valeur qu'on met dedans qui pose problème. En l'occurrence, on met comme valeur un pointeur sur un objet qui n'est plus dans la portée de la variable, donc selon le standard cette variable est invalide, ça a le même effet que si tu écrivais ceci:

    int *v = NULL;<br />if(g == 10)<br />{<br />&nbsp; &nbsp; int val = g * 2;<br />&nbsp; &nbsp; v = &amp;val;<br />}
    


    La variable "val" est créée dans le if puis détruire lorsque tu en sors, donc v aura un pointeur mais il pointera sur de la mémoire invalide...

    Et sinon, le seul intérêt de __block c'est permettre aux blocks qui l'utilisent d'écrire dans la variable, ça ne fait rien d'autre...
  • AliGatorAliGator Membre, Modérateur
    23:53 modifié #39
    Yep, j'ai vu ça 2 secondes après avoir posté mon message en allant dans la doc Apple pour vérifier le coup du "__block"... Du coup j'ai édité mon message au dessus en conséquence.
    Nouvelle hypothèse de solution : Block_copy() et Block_destroy()...?
  • psychoh13psychoh13 Mothership Developer Membre
    23:53 modifié #40
    Ouais, Block_copy() et Block_destroy(), c'est la solution. Il faut d'abord copier le block sur le tas pour qu'il reste valide et ensuite l'assigner à  la variable blk.
Connectez-vous ou Inscrivez-vous pour répondre.