Journal Genèse d'un journal

Posté par (page perso) . Licence CC by-sa
Tags :
33
9
sept.
2012

Mon journal précédent parlait de realloc dont on ne contrôlait pas la valeur de retour. Suite à ce journal j'ai été très surpris par le nombre de commentaires clamant que ce n'était pas important, que le noyau se chargerait de tuer le processus, que le programme planterait, … Je vais donc expliquer ce qui m'a amener à écrire ce journal.

Je codais un petit truc vite fait en C et j'avais besoin d'utiliser snprintf. Cette fonction prend, en paramètre, la taille de la zone mémoire. Si cette zone mémoire ne suffit pas, elle retourne la taille qui aurait été nécessaire pour inscrire toute la chaîne de caractère. Il est difficile de savoir la taille à l'avance. Un simple "%d" peut nécessité de 1 à plus de X caractères. Il faut donc, dans certains cas, le faire en deux fois. On évalue la taille qui sera généralement suffisante et on prévoit le cas où ce n'est pas suffisant. Ça donne un code qui ressemble à ça :

#define DEFAULT_SIZE 30
int size=0;
char * str=NULL;
char * tmp=NULL;

/* ... */

str=malloc(DEFAULT_SIZE * (sizeof * str));
if(str!=NULL) {
  size=snprintf(str, DEFAULT_SIZE, /* ... */);
  /* man page dit : "Thus, a return value of size or more means that the output 
   * was truncated." */
  if(size>=DEFAULT_SIZE) {
    /* snprintf retourne taille sans le '\0' */
    size++;
    tmp=realloc(str, size * (sizeof * str));
    if(tmp!=NULL) {
      size=tmp;
      snprintf(str, size, /* ... */);
    } else {
      free(str);
      str=NULL;
    }
  }
}

En écrivant la ligne tmp=realloc, j'avais commencé par écrire str=realloc. Je me suis arrêté net et j'ai pesté. J'ai pesté parce que je sais que c'est une erreur généralement acceptée. L'idée du journal m'était venue, il me manquait plus qu'un exemple concret dans un programme existant et utilisé.

Trouver un programme fut d'une simplicité enfantine. N'importe qu'elle programme qui semble manger beaucoup trop de mémoire est certainement bourré de ce genre d'erreur. J'avais remarqué conky il y'a bien longtemps. Avec un VmPeak de 281212 kB et un VmData de 82612 kB pour afficher, en texte, la date, la charge de la batterie, le nom du réseau Wifi et mon adresse IP, ça transpire le gaspillage.

Un git clone et un grep -R realloc * plus tard, j'avais une quinzaine d'exemple de realloc foireux. Soit tous les realloc, sans exception. Les malloc sont aussi foireux.

J'ai fait un patch pour ce que je pouvais, rapidement, corriger. VmPeak, sur la configuration par défaut, passe de 317916 kB à 211388 kB et VmData de 189372 kB à 123836 kB. Une amélioration notable après environ 4 ou 5 realloc corrigés.

Comme a commenté "pasBill pasGates", à propos de gérer correctement les retours de fonctions comme realloc ou malloc :

[…] c'est une question d'hygiene de base de mon point de vue.

Ne pas le faire, ce n'est pas seulement manquer d'hygiène, c'est surtout faire preuve de laxisme. Non, on ne peut pas compter sur le magicien "OOM Killer" de Linux. Le noyau a pu être compiler sans ça. Non, attendre un SEGFAULT pour quitter le programme n'est pas une solution. Et, surtout, ce n'est pas une optimisation que de gagner un ou deux cycles processeurs en enlevant un if(x==NULL).

"pasBill pasGates" a aussi dit :

Arreter le programme peut-etre oui, selon ce que le soft fait, mais il faut le faire proprement , pas avec un SEGFAULT ou un assert.

C'est exactement le cas. Votre programme de traitement de texte n'a pas assez d'espace pour le prochain caractère donc il plante sans possibilité de sauvegarder ? Ce n'est pas une solution.
Et l'excuse du programme non-critique n'en est pas une. Si un utilisateur décide d'utiliser votre programme, alors c'est un programme critique.

Le C est un langage qui impose cette gestion de mémoire. Si vous ne vous sentez pas de la faire, alors le C n'est pas pour vous.

  • # C

    Posté par (page perso) . Évalué à  5 .

    Ca fait longtemps que je n'ai pas fait de C sans ++, mais est-ce qu'il n'y pas une lib simple pour manipuler des chaînes de caractères facilement?

    Newton Adventure est sur Lumière Verte : http://steamcommunity.com/sharedfiles/filedetails/?id=187107465

  • # Je pense que tu confonds les cas où c'est nécessaire

    Posté par (page perso) . Évalué à  0 .

    Je suis d'accord avec le fait de vérifier les retours de malloc/realloc, mais uniquement quand la taille demandée est grande.
    Car oui, si tu demandes une plage de mémoire trop grande, tu peux ne pas l'obtenir sans que le programme fonctionne mal à côté car il reste de la mémoire disponible.

    Mais quand tu demandes quelques octets en mémoires, typiquement un petit tableau et qu'on te le refuse, on peut admettre sans problème qu'un tel cas n'arrive uniquement quand la machine est à genoux et qu'elle swappe à mort ou pire qu'elle est en train de planter lamentablement. Pour un petit programme de merde, ça ne vaut pas le coup de traiter un tel cas (qui n'arrive que rarement et la situation est en général dramatique en telle circonstance…).

    Bref, allouer un gros bloc ou un petit ne demande pas une gestion de la mémoire de même finesse, car les conséquences sur l'état du système à ce moment là sont très différents.

    • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

      Posté par (page perso) . Évalué à  10 .

      Il n'y a pas de finesse dans la gestion de mémoire. Soit on en fait, soit on en fait pas. Ne pas en faire c'est du mauvais travail : c'est ce genre de programme qui plante quand on fait un aperçu avant impression et qui nous font jurer et traiter le programmeur de tous les noms qui nous passent par la tête.

      Et tu ne peux rien admettre sur l'état de la machine en te basant sur le retour d'un malloc de 3 octets. La seule chose qu'on peut juger, c'est la qualité du travail du programmeur : bonne ou mauvaise.

      "It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell

    • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

      Posté par . Évalué à  10 .

      La plupart des APIs ont la possibilite d'echouer, pour diverses raisons (mauvais parametres, rates reseau, etc…)

      Faire :

      buffer=malloc(X)
      if (!buffer)
      {
        return ERROR_NOT_ENOUGH_MEMORY;   //  ou
        dwResult=ERROR_NOT_ENOUGH_MEMORY;
        goto cleanup;
      }
      
      

      ca prend quoi ? 5-10 secondes de plus, bref, rien de serieux niveau effort, et ca a l'avantage de permettre une gestion propre, avec un utilisateur qui ne voit pas ses softs exploser sans savoir pourquoi.

      • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

        Posté par (page perso) . Évalué à  1 .

        Le problème, c'est justement comme tu le dis si bien, la PLUPART des appels retourne des codes de retour en C, quasiment jamais vérifié, et que tu devrais toi aussi retourner du coup un code d'erreur.
        Ton code, ça va bien quand t'as 1 appel (et encore, le goto, c'est mal…). Mais je fais quoi quand j'empile 3, 4, 10, 42 appels ?
        Tes 5-10s se transforment en 5-10min puis 5-10j puis 5-10mois, puis… Et faut aussi inclure le coût de la maintenance et des évolutions futures.

        int fooBar() {
            if (foo()) {
                goto clean1;
            }
            if (bar()) {
                goto clean2;
            }
            return 0;
        
            clean1:
               cleanFoo(); // Merdeuuuuuuuuuuu, je peux planter aussi…
               return -1;
            clean2:
               cleanBar(); // Remerdeuuuuuuuuu, je peux planter aussi…
               return -1;
        }
        
        

        C'est moche, c'est inmaintenable, c'est soumis à une palanqué de possibilité de bugs, ça fait du code spaghetti, ça fait qu'on perd à chaque fois la possibilité de retourner des valeurs non code d'erreur et donc qu'on doit se taper des passages par référence partout…
        Et on ne sait généralement pas quoi faire sinon faire arrêter le programme de manière très moche dès qu'un truc retourne un truc non nul.

        On n'a aucune certitude que notre programme fonctionnera correctement sauf à écrire du code considéré comme ignoble par tout développeur standard.
        Et au final tout le monde s'en contre-fou de ces codes de retour, conduisant à des applis qui plantent/fuitent/buggent !

        • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

          Posté par (page perso) . Évalué à  4 .

          Je suis d'accord avec toi sur le reste de ton commentaire sauf :

          Ton code, ça va bien quand t'as 1 appel (et encore, le goto, c'est mal…). Mais je fais quoi quand j'empile 3, 4, 10, 42 appels ?

          Le goto est je pense une instruction intéressante uniquement pour la gestion d'erreurs. C'est justement plus lisible et propre que de tout faire à l'aide de if/else et compagnie… Là encore, c'est à utiliser avec parcimonie.

          Donc comme je disais plus haut, vérifier ce type de retour ne me semble pas intéressant pour une application qui a un but très peu critique et surtout dans les cas où s'il y a une erreur c'est que ton système est de toute façon à la rue et ne pourra pas fermer le reste proprement…

        • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

          Posté par . Évalué à  8 .

          Ah bah, oui, ton exemple contient déjà plusieurs erreurs !

          Le principe d´un appel à une fonction est que, soit elle réussit, et dans ce cas toutes les réservations (allocation mémoire, descripteurs de fichiers, sockets réseau, verrous, etc … ) qu´elle a à faire sont faites, soit aucune n´est faite. Donc, en sortie de foo(), soit tout est fait, soit rien n´est fait. Donc pas la peine de nettoyer foo() si ça échoue. Ditto pour bar(). Par contre, si bar() échoue, c´est que foo() a réussi, donc il faut libérer foo().

          Si une fonction comme foo() doit effectuer plusieurs réservations, et que cette fonction retourne un code d´erreur, comment savoir la raison de l´échec ? Il fait beaucoup plus sens de dépiler les réservations dans la fonction elle-même en cas d´erreur. La fonction de nettoyage undo_foo() est pertinente uniquement pour libérer après usage, pas en cas d´erreur.

          Par exemple, cette séquence me semble plus correcte :

          # return !0 on success, 0 on error
          int foo_and_bar() {
            int ret;
            if( !foo() ) {
              errno = -EFOO;
              goto foo_err;
            }
            if( !(ret=bar()) ) {
              errno = -EBAR;
              goto bar_err;
            }
            return ret;
          bar_err:
            undo_foo();
          foo_err:
            return 0;
          }
          
          

          Cette séquence respecte le même principe décrit ci-dessus : soit toutes les réservations ( foo et bar) sont effectuées, soit aucune ne l´est.

          Dans ce cas, le goto est vraiment très utile. C´est un des rares cas où ça l´est.

          C´est d´ailleurs, il me semble, le schéma utilisé dans le noyau Linux pour les traitements d´erreurs, comme dans beaucoup d´autres logiciels.

          Hop,
          Moi.

          • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

            Posté par (page perso) . Évalué à  1 .

            Ça ne change pas grand chose au final, ce genre de code est juste ignoble et devrait être catapulté en orbite à coup de bombes nucléaires (pour paraphraser Linus).
            Sur un bout de code à la con, on voit bien que pour 2 lignes utiles qui devraient conduire à une complexité cyclomatique de 1 et dont le pseudo-code serait

            fooBar() {
                foo()
                bar()
            }
            
            

            on en arrive à une 15aine de lignes de code et une complexité de 4.
            Sur un soft d'une complexité standard, je te laisse imaginer ce que ça donne…

            Idem en maintenance/évolution, alors que sur le pseudo-code on n'aurait qu'à ajouter une ligne entre foo() et bar(), en C on va devoir toucher aussi à toute la partie nettoyage/gestion d'erreur, aussi bien dans notre code que dans le code appelant. Idéal pour semer des régressions aux 4 coins de l'appli…

            Au niveau développement, l'utilisateur d'une API n'a aussi aucune garantie de gérer toutes les erreurs possibles et imaginables que peut lui retourner une fonction, sauf à aller lire la doc (généralement fausse et/ou incomplète) voire pire, carrément le code source…
            Et même s'il prend la peine d'aller lire la doc, il ne sait généralement pas quoi faire en cas d'erreur, sinon se mettre à remonter à l'appelant toutes les erreurs de sa couche basse.
            « Eh les gars, on est en train de recoder les check-exceptions là ! »

            Par extension, tout code en C DEVRAIT être codé selon ce principe, sauf à conduire aux différents problèmes soulevés PAR UN UTILISATEUR FINAL, c'est donc carrément le langage C qui devrait être mis en orbite !

            • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

              Posté par . Évalué à  9 .

              on en arrive à une 15aine de lignes de code et une complexité de 4.
              Sur un soft d'une complexité standard, je te laisse imaginer ce que ça donne…

              La realite est que cette methode est utilisee dans certains des plus gros projets software existants (Windows et Office au hasard ainsi que le kernel Linux) et que ca marche tres bien.

              Idem en maintenance/évolution, alors que sur le pseudo-code on n'aurait qu'à ajouter une ligne entre foo() et bar(), en C on va devoir toucher aussi à toute la partie nettoyage/gestion d'erreur, aussi bien dans notre code que dans le code appelant. Idéal pour semer des régressions aux 4 coins de l'appli…

              Et pourquoi tu aurais besoin de changer le code appelant ? Tout ce que le code appelant a besoin de savoir est que ca a rate, il peut aller de maniere plus granulaire pour peut-etre re-essayer, logger un message plus precis, … mais le simple fait de savoir que la fonction a echoue est suffisant, bref, un switch-case avec un 'default' fait le boulot ou un if (ret==success) else {} aussi.

              Au niveau développement, l'utilisateur d'une API n'a aussi aucune garantie de gérer toutes les erreurs possibles et imaginables que peut lui retourner une fonction, sauf à aller lire la doc (généralement fausse et/ou incomplète) voire pire, carrément le code source…

              La beaute est justement que c'est totalement optionel. Le minimum a faire est voir que la fonction a reussie (tu compares au code de reussite), ensuite si tu veux gerer specifiquement qqe erreurs ou toutes les traiter de la meme maniere, c'est ton choix, mais tu sauras que la fonction a echoue:

              DWORD dwResult=MyFunction();
              switch(dwResult)
              {
                case ERROR_SUCCESS:
                  break;
                case ERROR_BROKEN_PIPE:
                  delete[] FrameBuffer;
                  if (CheckConnection())
                    return Retry();
                  else return dwResult;
                case ERROR_NOT_ENOUGH_MEMORY:
                  log("pas assez de memoire\n");
                default:                        //toutes les erreurs non gerees specifiquement finissent ici
                  delete[] FrameBuffer;
                  return dwResult;
              };
              
              

              Sinon, ta phrase "l'utilisateur d'une API n'a aussi aucune garantie de gérer toutes les erreurs possibles et imaginables que peut lui retourner une fonction, sauf à aller lire la doc (généralement fausse et/ou incomplète)" est franchement horrible, oh mon dieu, l'utilisateur doit lire la doc ? Grande nouvelle, si il ne l'a lit pas, mieux vaut qu'il n'ecrive pas de code !

              • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                Posté par (page perso) . Évalué à  -6 . Dernière modification : le 09/09/12 à 21:14

                La realite est que cette methode est utilisee dans certains des plus gros projets software existants (Windows et Office au hasard ainsi que le kernel Linux) et que ca marche tres bien.

                C'est pas parce que tout le monde fait comme ça qu'il faut faire comme ça…
                Sinon, ça veut dire que Windows est meilleur que Linux ?

                Sinon, ta phrase "l'utilisateur d'une API n'a aussi aucune garantie de gérer toutes les erreurs possibles et imaginables que peut lui retourner une fonction, sauf à aller lire la doc (généralement fausse et/ou incomplète)" est franchement horrible, oh mon dieu, l'utilisateur doit lire la doc ? Grande nouvelle, si il ne l'a lit pas, mieux vaut qu'il n'ecrive pas de code !

                En temps que développeur d'une application, tu es utilisateur des API de plus bas niveau.
                Je ne parlais pas de l'utilisateur final, d'ailleurs ma phrase commence bien par « Au niveau développement ».

                • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                  Posté par . Évalué à  9 .

                  C'est pas parce que tout le monde fait comme ça qu'il faut faire comme ça…

                  Je serais ouvert a ecouter tes experiences sur le sujet relativement aux gros projets software et a leur success / qualite. Si tu as mieux, n'hesites pas.

                  Sinon, ça veut dire que Windows est meilleur que Linux ?

                  Vu que le kernel le fait aussi, comme je l'ai dit, non, ca veut dire que les 2 font comme il faut.

                  En temps que développeur d'une application, tu es utilisateur des API de plus bas niveau.

                  J'ai bien compris, et devines quoi, le developpeur doit lire la doc des APIs aussi, histoire de comprendre quels effets de bord ils peuvent avoir, dans quel cas ils peuvent se rater, etc…

                  • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                    Posté par (page perso) . Évalué à  -5 .

                    On va commencer par la fin, ça sera plus logique.

                    J'ai bien compris, et devines quoi, le developpeur doit lire la doc des APIs aussi, histoire de comprendre quels effets de bord ils peuvent avoir, dans quel cas ils peuvent se rater, etc…

                    J'ai aussi une info de dernière fraicheur pour toi : une doc n'est jamais à jour, toujours obsolète, toujours incohérente, toujours incomplète, voire même inexistante.
                    Celui qui te dira le contraire est un fou.
                    Si tu persistes encore à maintenir le contraire, prend n'importe quelle doc de ton choix et prouve-moi qu'elle est non seulement correcte, mais en plus exhaustive de tout ce qui peut se passer via n'importe quel cas d'utilisation.

                    La seule et unique chose qui peut décrire sans omission le comportement d'un code-source, c'est ce code-source lui-même, et lui seul.
                    Tout ce qu'un développeur peut te dire au sujet de ce code, et en particulier la doc, est tout autant soumis aux bugs du développeur que le code lui-même.
                    Un développeur peut très bien ne pas s'apercevoir d'un effet de bord de son code, et ne le documentera donc pas.

                    Je serais ouvert a ecouter tes experiences sur le sujet relativement aux gros projets software et a leur success / qualite. Si tu as mieux, n'hesites pas.

                    Je développe des softs toute la journée, et la qualité logicielle est justement une de mes préoccupations principale.
                    Et pour te résumer ma position de manière succinte : le C et consort, c'est mal; le duck-typing, c'est mal; le non-typé, c'est mal.

                    Pour le cas qui nous concerne, les check-exceptions sont pour moi le meilleur moyen de résoudre le problème qui nous inquiète.
                    Si on regarde le problème initial :
                    - Dans 99% des cas, on ne sait pas quelles erreurs peuvent arriver pour chaque ligne du programme. Cf supra, la doc ne peut pas aider sur ce point;
                    - Dans 99% des cas, on ne sait pas gérer une erreur au niveau où l'on est, et on devra la propager au niveau supérieur, jusqu'à ce que quelqu'un sache la gérer;
                    - Dans les cas les plus graves (null pointer exception, out of memory, buffer overflow…), on sait pertinemment que rien ne sert de tenter le moindre rétablissement, on est mort, autant s'arrêter.

                    Plutôt que d'avoir du code ignoble et inmaintenable à écrire sur chaque ligne et pour chaque erreur possible et/ou avoir à écrire la propagation supérieure et/ou avoir à gérer l'arrêt du programme, et ce sur chaque méthode, chaque appel de méthode et chaque programme, ça devrait être au compilateur de faire ce travail.
                    D'autant plus qu'il est très laborieux à faire, très difficile à faire écrire correctement et soumis à la bonne volonté du développeur (et de son planning/ses délais/ses finances !).

                    Si on prend les exceptions à la Java, on règle d'un coup tous les problèmes :
                    - Chaque méthode déclarant toutes les exceptions qu'elle peut lever, aucun risque d'en oublier une seule;
                    - Si une erreur peut arriver, alors on a que 2 choix, soit on sait la traiter et on la traite, soit on ne sait pas et on la laisse remonter, en déclarant que notre propre méthode peut échouer;
                    - Dans les cas les plus graves, le système lève une exception non-contrôlée, qui remontera toute seule toute la chaîne d'appel et arrêtera le programme.
                    C'est le compilateur qui fera tout le travail ingrat, proprement et sûrement, de manière mutualisée, évitant au développeur de réinventer la roue (carrée, de préférence…).

                    Voilà comment faire les choses proprement : déléguer au compilateur le maximum possible de vérification.
                    Un compilateur ne fera pas d'erreur, un développeur en fera obligatoirement une.

                    • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                      Posté par . Évalué à  10 .

                      J'ai aussi une info de dernière fraicheur pour toi : une doc n'est jamais à jour, toujours obsolète, toujours incohérente, toujours incomplète, voire même inexistante.
                      Celui qui te dira le contraire est un fou.
                      Si tu persistes encore à maintenir le contraire, prend n'importe quelle doc de ton choix et prouve-moi qu'elle est non seulement correcte, mais en plus exhaustive de tout ce qui peut se passer via n'importe quel cas d'utilisation.

                      Ah ben voila, quelle logique ! Une doc peut avoir des erreurs, donc en toute logique : il ne faut jamais utiliser la doc !
                      Tu sais qu'un compilateur peut avoir des bugs, pourquoi tu en utilises un alors ?

                      Tout ce qu'un développeur peut te dire au sujet de ce code, et en particulier la doc, est tout autant soumis aux bugs du développeur que le code lui-même.
                      Un développeur peut très bien ne pas s'apercevoir d'un effet de bord de son code, et ne le documentera donc pas.

                      Super, ca ne change absolument RIEN au fait que la doc est utile, qu'elle donne plein d'informations correctes, et qu'elle evite de faire des erreurs.

                      Je développe des softs toute la journée, et la qualité logicielle est justement une de mes préoccupations principale.
                      Et pour te résumer ma position de manière succinte : le C et consort, c'est mal; le duck-typing, c'est mal; le non-typé, c'est mal.

                      C'est pas ce que je t'ai demande, je t'ai demande en quoi je devrais suivre tes dires plutot que ceux des devs du kernel Linux ou de chez Microsoft. Tu as quoi comme experience de gros developpements de qualite et a succes qu'ils n'ont pas ?

                      Pour le cas qui nous concerne, les check-exceptions sont pour moi le meilleur moyen de résoudre le problème qui nous inquiète.
                      Si on regarde le problème initial :
                      - Dans 99% des cas, on ne sait pas quelles erreurs peuvent arriver pour chaque ligne du programme. Cf supra, la doc ne peut pas aider sur ce point;
                      - Dans 99% des cas, on ne sait pas gérer une erreur au niveau où l'on est, et on devra la propager au niveau supérieur, jusqu'à ce que quelqu'un sache la gérer;
                      - Dans les cas les plus graves (null pointer exception, out of memory, buffer overflow…), on sait pertinemment que rien ne sert de tenter le moindre rétablissement, on est mort, autant s'arrêter.

                      Tu as faux sur toute la ligne. Je dis bien, toute la ligne, et j'ai des millions de lignes de code presentes sur 1 milliard de machines qui le prouvent. Je t'ai meme presente plus haut pourquoi il n'y a pas besoin de connaitre toutes les erreurs pour pouvoir les gerer.

                      Plutôt que d'avoir du code ignoble et inmaintenable à écrire sur chaque ligne et pour chaque erreur possible et/ou avoir à écrire la propagation supérieure et/ou avoir à gérer l'arrêt du programme, et ce sur chaque méthode, chaque appel de méthode et chaque programme, ça devrait être au compilateur de faire ce travail.

                      Ah ben oui, parce que le compilateur est sense savoir comment tu veux gerer une erreur ? Le compilateur est meme sense savoir ce qu'est une erreur ? Serieux, tu developpes vraiment professionellement ?

                      Si on prend les exceptions à la Java, on règle d'un coup tous les problèmes :
                      - Chaque méthode déclarant toutes les exceptions qu'elle peut lever, aucun risque d'en oublier une seule;
                      - Si une erreur peut arriver, alors on a que 2 choix, soit on sait la traiter et on la traite, soit on ne sait pas et on la laisse remonter, en déclarant que notre propre méthode peut échouer;
                      - Dans les cas les plus graves, le système lève une exception non-contrôlée, qui remontera toute seule toute la chaîne d'appel et arrêtera le programme.
                      C'est le compilateur qui fera tout le travail ingrat, proprement et sûrement, de manière mutualisée, évitant au développeur de réinventer la roue (carrée, de préférence…).

                      Vraiment ? Tu connais les problemes du traitement en exceptions ? Sont cout en performance, la difficulte d'eviter des leaks et autres quand l'exception se produit apres que tu aies alloue des ressources, pris des locks, etc… ?
                      Le systeme par exception n'a pas vraiment plus d'avantages ni d'inconvenients, c'est une autre approche simplement.
                      Ton 'truc' ou les erreurs que tu ne connais pas sont remontees c'est exactement ce que je t'ai montre plus haut en C. Mon code fait exactement la meme chose : une erreur inconnue est remontee aussi.
                      Et non le compilo ne fait pas tout le travail ingrat, le compilo ne sait pas liberer des ressources prises avant l'exception, il ne sait pas liberer un lock, etc…

                      Voilà comment faire les choses proprement : déléguer au compilateur le maximum possible de vérification.
                      Un compilateur ne fera pas d'erreur, un développeur en fera obligatoirement une.

                      Un compilateur peut faire des erreurs (et oui, ca existe), mais en plus, un compilateur ne sait pas gerer les erreurs, du tout.

                • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                  Posté par . Évalué à  3 .

                  pour un coup je vais me faire l' "avocat du diable":

                  La realite est que cette methode est utilisee dans certains des plus gros projets software existants (Windows et Office au hasard ainsi que le kernel Linux) et que ca marche tres bien.

                  J'aurais même dit plus : cette méthode (ou ce type de méthode plus exactement parce que perso habituellement je préfère renvoyer le code d'erreur que de passer par errno), aussi bien en C mais aussi dans d'autres langages (mais peut-être plus en C,.. certes), est non seulement utilisée dans certains des plus gros projets logiciels, mais elle peut voire devrait l'être tout le temps (ou sinon des variantes avec gestion des exceptions par exemple) !

                  C'est pas parce que tout le monde fait comme ça qu'il faut faire comme ça…
                  Sinon, ça veut dire que Windows est meilleur que Linux ?

                  Faut lire sa parenthèse jusqu'au bout.. et je rajouterais, au niveau Linux, qu'il n'y a pas que dans le noyau qu'elle est utilisée, hooo que non.

                  Je ne parlais pas de l'utilisateur final, d'ailleurs ma phrase commence bien par « Au niveau développement »

                  Lui non plus il ne parlait pas de l'utilisateur final. Il parlait de l'utilisateur d'API (ou dit plus justement de bibliothèques de fonctions).
                  (quand bien même l'utilisateur final est aussi tenu de lire la doc de l'application qu'il utilise s'il veut savoir ce qu'elle fait et comment il faut s'y prendre).

            • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

              Posté par (page perso) . Évalué à  3 .

              Sur un bout de code à la con, on voit bien que pour 2 lignes utiles qui devraient conduire à une complexité cyclomatique de 1 et dont le pseudo-code serait

              fooBar() {
              foo()
              bar()
              }

              on en arrive à une 15aine de lignes de code et une complexité de 4.

              Ben, utilise des macros pour enrober tout ça !
              Ça me parait être une solution acceptable, si ce n'est pas LA méthode à utiliser…

              • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                Posté par (page perso) . Évalué à  4 .

                Une macro ne pourra jamais remplacer un traitement intelligent d'une erreur. Le fait est que le traitement intelligent et correct d'une erreur est quelque chose de complexe (donc difficile à faire bien), coûteux en temps de dev, mais en même temps, indispensable dès qu'on arrive sur des gros logiciels.

                Joel On Software y consacre un article pas trop mal d'ailleurs, que j'ai la flemme de retrouver.

                Exception exhaustif à la java (je déclare tout ce que je lève), laxiste à la python (chaque ligne de code peut me balancer 24 exceptions différentes) ou code de libération en C avec des goto, aucune méthode n'est parfaite mais ce qui est sur, c'est que ça demande de l'investissement.

                Suivant la taille du logiciel et son utilité, cet investissement est à mon sens pas toujours justifié.

                Pour élargir le débat:

                On peut aussi prendre le cas d'erreur du disque plein, ou du logiciel qui s'execute sur une partition sans les droits d'écritures. C'est des cas réels, compliqués à gérer, dans lesquels il faut informer l'utilisateur pour qu'il resolve intélligemment son problème. Dans ces deux cas, un simple crash est une mauvaise porte de sortie car l'utilisateur doit être informé de la situation pour pouvoir la régler.

                Sinon le nouveau langage Go introduit un truc intéressant: des routines qui sont exécutées à la sortie de la fonction, dans l'ordre inverse où elles sont été déclarées. La logique est bien de mettre toute la libération des ressources dans ces routines, de façon à ce qu'elles soit libérées automatiquement en cas d'erreur.

                Mais là encore, d'une part il faut être très rigoureux et d'autre part, il y a des fois où la libération de ressource est compliquée et même avec toute la structure du langage, on ne peut pas se passer d'une bonne prise de tête pour traiter avec honneur tous les cas tordus.

                • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                  Posté par (page perso) . Évalué à  1 .

                  Une macro ne pourra jamais remplacer un traitement intelligent d'une erreur.

                  Qui parle de remplacement intelligent ?
                  Il s'agit d'un remplacement systématique pour un cas précis : le realloc.
                  J'imagine que tous les reallocs ont une gestion d'erreur identique, et que c'est donc un devoir d'enrober ça dans une macro.

                  Si tu veux une gestion d'erreur sans te faire chier, tu changes de langage ; mais en aucun cas¹ tu ignores les erreurs ou tu les traites mal comme c'était le cas dans le code initialement cité.

                  ¹ : sauf en Perl :)

            • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

              Posté par . Évalué à  6 .

              on en arrive à […] une complexité de 4

              En fait, non. Coupons un peu les cheveux en quatre, ça ne prend pas longtemps de s´amuser un peu. ;-)

              Le raisonnement suivant suit la description de la page en anglais de Wikipedia, qui est plus fournie que la page en français sur la complexité cyclomatique.

              Soit on considère que les deux return représentent deux points de sortie distincts, et on a le graphe suivant:

              E ----> foo? --(foo KO)----------------------------------+
                       |                                               |
                       +--(foo OK)--> bar? --(bar KO) ----> undo_foo --+--> X1
                                       |
                                       +----------------------------------> X2
              
              

              Dans ce cas, la formule donne: M = π - s + 2, avec :

              • π = 2 points de décision: foo? et bar?
              • s = 2 points de sortie: X1 et X2
              • soit M = 2 - 2 + 2 = 2

              Maintenant, si on considère les deux return comme étant un seul et unique point de sortie, le graphe devient:

              E ----> foo? --(foo KO)-------------------------------------+
                       |                                                  |
                       +--(foo OK)--> bar? --(bar KO) ----> undo_foo --+  |
                                       |                               |  |
                                       +-------------------------------+--+--> X
              
              

              Et la formule donne :

              • π = 2 points de décision: foo? et bar?
              • s = 1 point de sortie: X
              • soit M = 2 - 1 + 2 = 3

              Donc, pas 4. Et les mouches ont mal, maintenant. ;-)

              Hop,
              Moi.

    • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

      Posté par (page perso) . Évalué à  4 .

      arrive uniquement quand la machine est à genoux et qu'elle swappe à mort ou pire qu'elle est en train de planter lamentablement.

      Il est évident que tu as oublié un autre cas, je te laisse chercher lequel.

      * Ils vendront Usenet quand on aura fini de le remplir.

      • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

        Posté par . Évalué à  1 .

        Quand on code pour Windows ?

        THIS IS JUST A PLACEHOLDER. YOU SHOULD NEVER SEE THIS STRING.

      • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

        Posté par . Évalué à  3 .

        Utilisation de ulimit ?

        • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

          Posté par . Évalué à  1 .

          Ton app charge un fichier qui il se trouve est corrompu.
          Le format contient quelque chose du genre [taille du tableau][tableau]

          Ton app, pour charger le tableau va lire la taille et essayer de faire une allocation pour y stocker le tableau. Si la taille est corrompue (genre 0xFFFFFF0 ou autre truc enorme), ton soft va essayer de faire une allocation enorme, qui va rater meme si ta machine a encore plein de RAM dispo.
          Est-ce que le soft devrait planter dans un cas pareil ? Non, il devrait voir qu'il n'arrive pas a lire le fichier et renvoyer une erreur a l'utilisateur mais si on lit certains ici, planter est la solution parait-il…

          • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

            Posté par (page perso) . Évalué à  0 .

            Est-ce que tu peux lire nos messages avant de les critiquer ?

            Ton app charge un fichier qui il se trouve est corrompu.

            Je n'ai jamais chargé un fichier avec malloc directement, j'aimerais que tu m'expliques le rapport entre allouer une zone mémoire et un fichier. Tu charges le fichier avec fopen normalement… Or je ne parle que de malloc ici.

            Le format contient quelque chose du genre [taille du tableau][tableau]

            Je parle d'une petite allocation de quelques octets, pas d'une matrice…

            Ton app, pour charger le tableau va lire la taille et essayer de faire une allocation pour y stocker le tableau. Si la taille est corrompue (genre 0xFFFFFF0 ou autre truc enorme), ton soft va essayer de faire une allocation enorme, qui va rater meme si ta machine a encore plein de RAM dispo.

            Comme je le disais, je parle de petite allocation. Car quand ta machine n'aura pas 10 octets à allouer, je peux te dire qu'elle doit être en situation très délicate et je doute que tu puisses faire quoique ce soit à ce moment là. Je ne parle pas d'allocations de plusieurs Mo ou plus qui peuvent échouer même en cas de RAM disponible.

            Donc merci de mieux lire les messages et de comprendre la situation exposé au lieu de parler de situations dont je n'ai jamais rejeté l'utilité de vérifier le retour de malloc.

            • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

              Posté par . Évalué à  0 .

              Je n'ai jamais chargé un fichier avec malloc directement, j'aimerais que tu m'expliques le rapport entre allouer une zone mémoire et un fichier. Tu charges le fichier avec fopen normalement… Or je ne parle que de malloc ici.

              Quand tu charges des donnees d'un fichier, tu les stockes ou ? En memoire le plus souvent, memoire qu'il faut allouer.

              Je parle d'une petite allocation de quelques octets, pas d'une matrice…

              Si tu sais d'avance qu'elle est petite oui, mais combien y a t'il de cas ou tu sais de maniere garantie qu'elle sera petite ? Combien sont de taille dynamique, ou la valeur vient de l'exterieur (Content-Length header d'une requete HTTP, taille de fichier, taille de tableau lue dans un fichier, valeur specifiee par l'utilisateur, etc…)

              • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                Posté par (page perso) . Évalué à  -1 .

                Je parle de cas où tu sais d'avance que c'est petit…
                Arrête de faire semblant de ne pas comprendre mes propos…

                • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                  Posté par . Évalué à  2 .

                  Donc tu vas faire des checks seulement la moitie du temps dans ton code ? Tu vas te retrouver avec un code qui se comporte correctement la moitie du temps et qui explose l'autre moitie ? Tu vas te poser la question a chaque fois de ce que tu dois faire (verifier ou pas) plutot que simplement le faire systematiquement ?

                  Desole mais je ne vois vraiment pas ce que t'y gagne.

                  • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                    Posté par . Évalué à  -1 .

                    je comprends pas que tes commentaires soient autant surnotés, si tu codes comme un goret, et que tu mets directement ce que tu scannes en paramètre d'un malloc, un fichier corrompu fera craché ton programme même avec un malloc de 100 qui va réussir …

                    Il y a une différence entre traiter les erreurs et s'occuper d'un malloc qui retourne NULL. Quand tu scannes ton fichier, c'est là que tu gères l'erreur. Personne n'a sorti un exemple de code critique qui gérait spécifiquement le cas malloc retourne null.

                    • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                      Posté par . Évalué à  3 .

                      je comprends pas que tes commentaires soient autant surnotés, si tu codes comme un goret, et que tu mets directement ce que tu scannes en paramètre d'un malloc, un fichier corrompu fera craché ton programme même avec un malloc de 100 qui va réussir

                      Lorsque tu lis le fichier, et que le format te dit que le champs 'longueur' du tableau est de 4 octets, tu vas faire quoi comme test dessus pour savoir ou non si tu fais l'allocation ? C'est quoi la valeur maximale a accepter ? Tu pourrais bien verifier la taille du fichier d'abord, mais ca ne marche pas si c'est un tableau en sortie de decompression (tu ne connais pas le % de compression) ou qui vient d'un stream genre socket reseau…

                      Personne n'a sorti un exemple de code critique qui gérait spécifiquement le cas malloc retourne null.

                      Tu regardes l'OS que tu utilises et celui qui represente 90% du marche desktop, il en sont rempli.

                      • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                        Posté par . Évalué à  -3 .

                        donc on peut rien faire, il faut se fier au malloc pour savoir si ton fichier est corrompu… Regarde la structure de n'importe qu'elle système de fichier ou conteneur de données : les allocations se font par chunk. t'es libre de mapper directement tes données en mémoire depuis ton fichier, mais en cas de corruption, c'est pas le malloc qui échoue qui va te renseigner.

                        Dans l'OS que j'utilise, init/systemD/OpenSSL ne font rien (c.a.d. sortent ou attendent), sous Windows, bah tu me montres un exemple, j'ai pas accès au code.

                        • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                          Posté par . Évalué à  -1 .

                          a) Oui, si dans ton fichier les seuls bytes modifies sont ceux du champs 'longueur', tu vas faire quoi pour verifier si c'est corrompu ? Comment tu vas faire pour savoir que ton tableau compresse il ne fait pas 0x101fe040 bytes une fois decompresse mais 0x001fe040 bytes ? Donnes moi la reponse si tu la connais…
                          Les allocations par chunk ca marche uniquement quand il est acceptable d'avoir les donnees en blocs separes, pour une image par exemple c'est souvent pas acceptable car ca complique enormement les manipulations sur les pixels sans parler du fait que ca empeche l'affichage…
                          Quand a mapper le fichier, si le contenu est compresse, ca te fait une belle jambe de le mapper…

                          b) Tu as montre toi meme le code d'init qui verifie la valeur de retour de malloc…
                          Pour Windows t'as pas besoin de sources, suffit de desassembler, regarder les instructions apres l'appel a malloc et voir qu'il fait une comparaison sur le registre eax qui est l'endroit ou la valeur de retour se trouve.

                          • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                            Posté par . Évalué à  -2 . Dernière modification : le 11/09/12 à 11:54

                            A/ n'importe quoi : d'abord on parle de gérer par le code de sortie de malloc au cas ou on donne un paramètre corrompu … mais bon, on est pas à ça près, Je te prends au mot : si mon programme fais un écrasement mémoire, n'est-il pas plus judicieux de cracher ou l'erreur à lieu, plutôt que de se rattraper aux branches pour crasher 0.1 seconde plus tard (mais trop tard, tu sais plus ou est l'erreur à eu lieu)..
                            regarde la sérialisation de données (ou même, plus généralement n'importe qu'elle structure de données du type FS, compression ou autres), tu croies vraiment qu'il vaut mieux vérifier le retours d'un malloc plutôt que, à la lecture, vérifier les bornes avec une séquence d'octets magigues (du style B16B00B5)

                            B/ Je pense que tu es payé à me faire perdre mon temps (et aux autres), il n'y a pas de gestion d'erreur spécifique comme je le dis : ces programme ne font rien, RELIS.

                            • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                              Posté par . Évalué à  1 .

                              A) Mais de nouveau, si le parametre 'longueur' est corrompu, comment tu le sais quand tu lis ton fichier ?
                              Je t'ai donne un exemple, j'aimerais bien que tu me dises comment gerer cet exemple sans verifier la valeur de retour de malloc :

                              [longueur du buffer decompresse: 4 octets][buffer compresse]
                              
                              

                              Tu dois allouer le buffer de destination pour decompresser le buffer, comment tu verifies si le champs longueur est OK ? La reponse est simple: tu ne peux pas, tout ce que tu peux faire est essayer d'allouer la taille demandee, retourner une erreur si ca ne marche pas, et t'assurer quand tu decompresses que tu ne depasses pas la taille allouee.

                              Mais si tu as un meilleur moyen, fais seulement, eclaires ma lanterne.

                              Tes bornes dans la serialisation elles vont servir a quoi ? A rien. Tu devrais voir a quoi ressemblent les failles de securite dans les formats :
                              a) Je modifies un champs pour y mettre une valeur inattendue
                              b) Je recalcules les checksums, etc… quand il y en a
                              c) J'envoies le fichier a un gars
                              d) A la lecture du fichier, le soft explose en vol

                              Le format du fichier ne peut pas etre de confiance car il vient de l'exterieur. La seule chose sur laquelle tu peux compter c'est ton propre code.

                              B) Tu as pose le code d'init toi-meme : Il fait un loop en verifiant si l'alloc reussit et si non il attend et re-essaie. Si on ecrivait cela comme nombre de gens ici le suggerent, init crasherait au 1er alloc qui rate.

                              • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                                Posté par . Évalué à  -5 . Dernière modification : le 12/09/12 à 11:00

                                1/ Franchement, relis. Puis tu as 2 de QI ou quoi?? si la longueur est supérieur à la taille de ton fichier, tu te doutes qu'il y a un problème non? C'est pas moins c?? que de voir si ton malloc foire.

                                regarde du code sérialisant des objets, les conteneurs vidéo ou les spec de n'importe quel format de compression, ou la structure de n'importe qu'elle FS.

                                2/ J'ai l'impression que tu me cherches, Je perds encore une fois mon temps : il n'y a pas de gestion d'erreur spécifique à chaque malloc, regarde le code, TOUS les programmes sérieux/critiques ne font rien d'autre que : soit attendre, soit sortir. Pas de message d'erreur inutile spécifique, pas de je désalloue un truc inutile pour pouvoir continuer, pas de : oui alors je vais revenir dans un état cohérent ou je ne sais quelle autre Microsofterie. Sous Linux, lorsque malloc retourne NULL, t'es même pas sûr de pouvoir faire un appel de fonction…

                                Maintenant regardes ce qu'il se passe dans le imalloc de init au cas ou un autre goret coderait comme toi : ça part en boucle infinie! Mais c'est pas génial ça!

                                Dans le monde réel te répondre c'est déjà un peu partir en boucle, en ayant l'impression de devenir un peu moins intelligent à chacun de tes messages.

                                • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                                  Posté par . Évalué à  2 .

                                  Si si c'est bien utilisé en pratique une gestion plus fine des erreurs malloc, par exemple dans le code d'inkscape

                                  Il y a une macro SAFE_MALLOC qui fait un goto sur malloc_error si malloc renvoie NULL, et les fonctions qui appellent SAFE_MALLOC se chargent de libérer correctement la mémoire après le goto et de renvoyer une sortie d'erreur. Pas de crash envisagé donc, et même plutôt le contraire. Il s'agit du codee pour allouer de nouvelles courbes, il vaut mieux en effet ne pas crasher le soft simplement parce qu'une nouvelle courbe a été crée..

                                • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                                  Posté par . Évalué à  2 .

                                  si la longueur est supérieur à la taille de ton fichier, tu te doutes qu'il y a un problème non?

                                  Exemple donnée plus haut : sur le disque, le fichier est compressé, et tu le lit directement via une bibliothèque qui fait la décompression a la volée. Cette bibliothèque ne te donne pas la taille réelle des données (souvent elle ne le peut pas). Donc la taille du fichier sur disque ne donne aucune information pertinente, elle sera probablement inférieure à la longueur lue, sans que ça pose de problème.

                                  • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                                    Posté par . Évalué à  -2 . Dernière modification : le 12/09/12 à 17:07

                                    Ok dans ce cas tu fais ce que j'ai dis plus haut. tu mets un marqueur après ton buffer compressé ou un checksum sur la taille.

                                    • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                                      Posté par . Évalué à  -2 .

                                      je peux pas éditer le précédant commentaire (on peut ajouter un checksum mais pas de marqueur), mais en fait si la taille est de 100 pour les données non compressé, et que le fichier est corrompu et la taille devient 10, le malloc foire pas et tu as un bel écrasement lorsque tu vas écrire les données.

                                      ça marche par accident de tester si le malloc est NULL dans ce cas.

                                      • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                                        Posté par . Évalué à  -2 .

                                        De nouveau non tu ne comprends rien, le checksum ou le marqueur ne changent rien

                                        A) Si je veux t'attaquer, je peux t'envoyer un fichier avec un mauvais champs longueur et une checksum correcte. Mais c'est de toute facon pas important parce que si le format du fichier ne comprend pas de checksum ou de marqueur, ben voila, tu ne peux pas en rajouter, tout simplement car le format n'en a pas et tu aurais des lors un fichier non conforme.

                                        b) Ecraser le buffer n'est pas le probleme

                                        [taille: 100'000][buffer compresse de 50'000 octets, qui disons va etre decompresse a 120'000 octets]
                                        
                                        

                                        Rien ne t'empeche de traiter ca correctement, tu sais que tu as alloue 100'000 bytes, suffit de t'assurer que tu n'ecris pas plus de 100'000 bytes quand tu decompresses et retourner une erreur si il y a des donnees restantes.

                                        [taille: 1'900'000'000][buffer compresse de 50'000 octets, qui disons va etre decompresse a 120'000 octets]
                                        
                                        

                                        Ici, avant meme de commencer a decompresser, ton malloc a de tres grandes chances de retourner NULL sur x86 et tu vas exploser immediatement si tu ne verifies pas la valeur de retour.

                                • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                                  Posté par . Évalué à  -2 .

                                  1) Je te suggeres de rester poli, surtout que tu as faux sur toute la ligne, et on est plusieurs a te le dire.
                                  2) Le code serialisant des objets, les conteneurs video, etc… ont tous ces problemes, je te suggeres d'aller regarder les patches pour failles de securite dans libjpeg / libpng etc… cf. http://blog.cr0.org/2009/05/write-once-own-everyone.html par exemple.

                                  3) Il y en a , tu l'as montre toi meme : attendre est de la gestion d'erreur, tu verifies la valeur retournee et tu evites un crash. Quelqu'un d'autre t'as montre plus bas qu'Inkscape gerait cela aussi et plein d'autres softs font de meme

                                  Maintenant regardes ce qu'il se passe dans le imalloc de init au cas ou un autre goret coderait comme toi : ça part en boucle infinie! Mais c'est pas génial ça!

                                  Tu es un sacre clown quand meme, tu as vu ou que j'ecrirais un code qui part en boucle infinie ? Tu as deja vu mon code ?
                                  La difference entre toi et moi c'est que moi mon code tourne sur un milliard de machines, y compris des serveurs surcharges, et la plupart du temps sans probleme. Toi tu es visiblement bon pour insulter les gens, et beaucoup moins pour comprendre comment programmer proprement.

                    • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                      Posté par . Évalué à  6 .

                      je comprends pas que tes commentaires soient autant surnotés

                      Je dois avouer que je n'ai jamais autant pertinenté pbpg et que ça me fait tout drôle.. (pourtant dieu sait que dans d'autres type de journaux je serais un des premiers à l'inutiliser)

                      Il semblerait donc que ce journal, d'ordre purement technique, soit suivi par beaucoup de gens du domaine et que ces gens soient en majorité d'accord avec ce qu'il énonce à longueur de commentaires.

                      d'ailleurs si on pouvait cesser de le relancer ça me ferait plaisir (parce que lui il a bien compris qu'il était sur un bon filon de + et ne s'en prive pas - à raison dirais-je) ;)

                      Je dirais qu'en résumé, qu'un soft soit "critique" ou pas (un soft peut-être non critique pour son auteur mais être utilisé dans une chaîne/situation critique par certains utilisateurs), vérifier les codes d'erreur des fonctions (externes ou pas) qu'on appelle et agir un tant soit peu en conséquence (au minimum un 'perror("fonctionX") ; exit(1);', ou un truc équivalent) c'est ce qu'il faudrait toujours faire, amha.

                      • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                        Posté par . Évalué à  1 .

                        C'est tout la question de savoir jusqu'à quel point il faut vérifier les entrées (ou les sorties); à tout vouloir vérifier, dans tous les moindres détails (y compris ceux qui ne sont pas censés arriver), on arrive à un ratio de ligne vérifiant les entrés très largement supérieur aux lignes utile au calcul / traitement;

                        Cela peut se ressentir à l'exécution; tout comme une certaine lourdeur dans le code et sa maintenabilité; j'ai un collègue qui parle de GIGO, (garbage in, garbage out), pour gérer des entrée hors normes.

                        Pour prendre le cas de sqrt() (fonction externe), je ne vais pas m'amuser à vérifier son retour, à la rigueur je test le nombre que je met en entrée, mais à partir du moment où je lui file un nombre positif, je sais que j'aurais un truc correcte en sortie.

                        Par contre pour le coup du malloc/realloc, j'aurais tendance à vouloir vérifier le résultat, je suis particulièrement sensible à ce genre de blague (par contre j'utiliserai asprintf pour faire ce qui est demandé ;

                        mon préféré restera une fonction c récursive

                        int a foo(int b){
                          static int *tab=NULL;
                          ...
                          tab[n]=foo(x);
                          ... 
                        }
                        
                        

                        avec un realloc dans foo; une pure merveille (de bug vicieux) ;)

                        Il ne faut pas décorner les boeufs avant d'avoir semé le vent

                      • [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

                        Posté par . Évalué à  -3 .

                        ça veut dire quoi "beaucoup de gens du domaine", du domaine de quoi?

                        tu la trouves technique cette discussion au point qu'il faille ranger les intervenants dans un "domaine"?? pas moi. C'est un peu le b.a. ba.

                        Il ne s'agit pas de ne pas vérifier les erreurs, il s'agit de ne pas pondre du code pour agir spécifiquement au cas ou un malloc retourne null dans un OS moderne. Si tu saisies pas la différence?

  • # asprintf

    Posté par (page perso) . Évalué à  3 .

    Si on est prêt à utiliser les extensions GNU, asprintf fait des merveilles. Sinon, il y a la gnulib qui apporte la couche de portabilité manquante et qui s'intègre merveilleusement bien à un build autotool.

  • # criticité

    Posté par (page perso) . Évalué à  10 . Dernière modification : le 09/09/12 à 12:55

    Non, on ne peut pas compter sur le magicien "OOM Killer" de Linux. Le noyau a pu être compiler sans ça.

    Le OOM killer se tune par sysctl (overcommit_memory knobs) et /proc (oom_adj), pas à la compilation.
    Et ce n'est pas un tour de magie, mais plutôt un hack ignoble (et compter dessus est stupide et non portable).

    C'est exactement le cas. Votre programme de traitement de texte n'a pas assez d'espace pour le prochain caractère donc il plante sans possibilité de sauvegarder ?

    Tu sous-estime la complexité.
    OK, tu testes ton pointeur, tu vois qu'il est NULL.
    Mais pour faire les choses proprement, par exemple prévenir l'utilisateur, ou sauvegarder le fichier, et bien il faut de la mémoire.
    Bah oui, créer un popup, ça demande de la mémoire. fprintf() utilise malloc(), tout comme fwrite() (oui je connais setvbuf()), etc. Quand tu y penses, sans malloc(), il ne te reste plus grand chose de disponible.

    En fait c'est encore bien pire que ça, parce que sur OOM (je ne parle pas du cas où tu es limité par RLIMIT_DATA/RLIMIT_AS…), quasiment n'importe quel appel système peut foirer: fork() (vérifié sur OpenIndiana), write(), close()…

    Donc la morale, c'est que tu ne pourras probablement pas sauvegarder ton fichier, et que tu risques de le corrompre si tu essaies.

    C'est pour cela que dans les applications critiques, il faut utiliser des opérations atomiques, ou des transactions. Par exemple, ton traitement de texte bosse sur une copie du fichier, et ensuite fait un truc du style :

    fsync(fd);
    close(fd):
    rename("monfichier.tmp", "monfichier");
    fsync(<répertoire parent du fichier>);
    
    

    Au moins là tu es tranquille.
    Par ce que tu vois le cas d'OOM, mais ton process peut se faire tuer par un SIGKILL ou n'importe quel autre signal, et dans ce cas là tu ne pourras pas catcher.

    Mais ce genre de chose est complexe, et ce n'est probablement pas la peine de le faire sur un programme qui se content de te donner ta consommation en CPU/RAM/bande passante. Il n'a pas d'état, pas d'effets de bords, il peut crasher n'importe quand.
    Par contre, mettre un assert() est pas mal pour éviter une éventuelle faille de sécurité due au déréférencement d'un pointeur NULL.

    • [^] # Re: criticité

      Posté par . Évalué à  8 .

      Mais pour faire les choses proprement, par exemple prévenir l'utilisateur, ou sauvegarder le fichier, et bien il faut de la mémoire.
      Bah oui, créer un popup, ça demande de la mémoire. fprintf() utilise malloc(), tout comme fwrite() (oui je connais setvbuf()), etc. Quand tu y penses, sans malloc(), il ne te reste plus grand chose de disponible.

      Ca depend des cas, comme tout. Si ton soft voit son alloc echouer alors que tu es en train de charger un 14eme document, ben il abandonne et vide tout ce qu'il a alloue pour le 14eme, et il evite de tout crasher et forcer l'utilisateur a rouvrire les 13 premiers documents, c'est un avantage indeniable.

      Bref, gerer les mallocs qui ratent, ca ne coute quasiment rien, et ca peut rapporter gros.

    • [^] # Re: criticité

      Posté par (page perso) . Évalué à  8 .

      Le OOM killer se tune par sysctl (overcommit_memory knobs) et /proc (oom_adj), pas à la compilation.

      Dans les noyaux 2.4 on pouvait ne pas le compiler, dans les versions suivantes je ne sais pas.

      Tu sous-estime la complexité.
      OK, tu testes ton pointeur, tu vois qu'il est NULL.
      Mais pour faire les choses proprement, par exemple prévenir l'utilisateur, ou sauvegarder le fichier, et bien il faut de la mémoire.

      C'est pour ça que tu vas allouer de la mémoire au lancement du programme pour avoir une "réserve" pour ce genre de cas. Si tu ne peux pas allouer un peu de mémoire, tu pioches dans ta réserve de secours pour gérer correctement l'erreur. Je ne sous-estime pas la complexité, je la reconnais mais la complexité n'est pas une excuse pour ne rien faire.

      Mais ce genre de chose est complexe, et ce n'est probablement pas la peine de le faire sur un programme qui se content de te donner ta consommation en CPU/RAM/bande passante. Il n'a pas d'état, pas d'effets de bords, il peut crasher n'importe quand.

      Tu en sais quoi qu'il pourrait crasher n'importe quoi ? Est-il utilisé dans un environnement plus complexe où un crash pourrait avoir des répercussion plus importantes ? Tu n'en sais rien, l'utilisateur part du principe que les programmeurs sont civilisés et qu'il peut utiliser le programme comme bon lui semble, y compris l'utiliser comme élément critique. Ou alors le programme indique clairement : "attention ce programme ne gère pas du tout la mémoire, il ne faut donc pas l'utiliser à part si vous n'en avez pas besoin" !

      Par contre, mettre un assert() est pas mal pour éviter une éventuelle faille de sécurité due au déréférencement d'un pointeur NULL.

      Sauf que ton assert est viré lors de la compilation en mode production. Donc utiliser un assert pour des questions de sécurité … je trouve ça bizarre. Mais on va me dire que toutes les possibilités existantes sont testées donc on peut utiliser un assert, c'est ça ?

      Soyons sérieux, tu gueules si un appareil électrique grille à la première surtension. Pourtant il est prévu pour le réseau standard, donc pourquoi mettre un fusible ? Et finalement, c'est juste un gadget, pas un appareil critique, ce n'est pas grave s'il grille : il suffit de le ramener sous garantie. Sauf que ta maison à cramer à cause de ce raisonnement.

      "It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell

      • [^] # Re: criticité

        Posté par (page perso) . Évalué à  0 .

        C'est pour ça que tu vas allouer de la mémoire au lancement du programme pour avoir une "réserve" pour ce genre de cas.

        Bah voyons, montre-moi tu empêches fprintf() ou un appel système de retourner ENOMEM en cas de OOM, je suis curieux.

        Tu en sais quoi qu'il pourrait crasher n'importe quoi ?

        Tout les process peuvent crasher, à n'importe quel moment : OOM, signal, mémoire défectueuse…
        Le seul critère est l'impact du crash : et dans ce cas, un logiciel non critique, sans persistance de données, sans effets de bords, l'impact est négligeable.

        Ou alors le programme indique clairement : "attention ce programme ne gère pas du tout la mémoire, il ne faut donc pas l'utiliser à part si vous n'en avez pas besoin" !

        OK, donc explique-moi ce que tu pourrais faire de plus qu'un _exit() en cas de OOM sur ce programme précis (parce qu'on parle de celui-là, toute la question est de la pertinence d'une telle gestion en fonction de la criticité). Si ton programme ne peut plus fonctionner correctement, autant le terminer pour remonter l'erreur que de continuer et faire n'importe quoi, non (encore une fois, je parle de programmes non critiques) ?

        Soyons sérieux, tu gueules si un appareil électrique grille à la première surtension. Pourtant il est prévu pour le réseau standard, donc pourquoi mettre un fusible ? Et finalement, c'est juste un gadget, pas un appareil critique, ce n'est pas grave s'il grille : il suffit de le ramener sous garantie. Sauf que ta maison à cramer à cause de ce raisonnement.

        OK, quand je vois ce genre de comparaisons, je me dis que ce n'est pas la peine de discuter…
        Tu compares une installation électrique à un gadget GUI qui t'affiche ta consommation CPU.
        Si tu ne fais pas la différence de criticité entre les deux, je ne peux rien pour toi…

        Si en Java, OutOfMemoryError est une Error, et pas une checked exception, il y a une raison…

        Par ailleurs, je ne vois toujours pas le lien entre realloc() et la fuite mémoire, tu devrais utiliser valgrind au lieu de patcher le code au petit bonheur la chance…

        • [^] # Re: criticité

          Posté par (page perso) . Évalué à  7 .

          Par ailleurs, je ne vois toujours pas le lien entre realloc() et la fuite mémoire, tu devrais utiliser valgrind au lieu de patcher le code au petit bonheur la chance…

          Dans certains cas, le code faisait plus ou moins return realloc(ptr, /* ... */);. La suite du code gère correctement le pointeur NULL, donc ça ne crash pas, mais on a plus de référence pour libérer la mémoire ensuite.

          "It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell

          • [^] # Re: criticité

            Posté par (page perso) . Évalué à  2 .

            Encore une fois, je doute très fortement que ce soit la cause des fuites mémoire.
            malloc()/realloc() qui retourne NULL, c'est très rare, il faut vraiment être en OOM ou atteindre RLIMIT_DATA/RLIMIT_AS.
            Utilise "ltrace -e malloc,realloc" pour t'en convaincre…

            Et surtout valgrind…

        • [^] # Re: criticité

          Posté par . Évalué à  1 .

          OK, donc explique-moi ce que tu pourrais faire de plus qu'un _exit() en cas de OOM sur ce programme précis (parce qu'on parle de celui-là, toute la question est de la pertinence d'une telle gestion en fonction de la criticité). Si ton programme ne peut plus fonctionner correctement, autant le terminer pour remonter l'erreur que de continuer et faire n'importe quoi, non (encore une fois, je parle de programmes non critiques) ?

          Mais qui t'a dit qu'il ne peut plus fonctionner correctement ? Ce soft peut liberer des buffers qu'il detient et re-essayer, simplement abandoner le chargement du fichier qu'il etait en train de lire et continuer de travailler avec les autres documents qu'il a charge, etc…

          • [^] # Re: criticité

            Posté par (page perso) . Évalué à  1 . Dernière modification : le 09/09/12 à 21:23

            On parle de logiciels non critiques et toi tu parles de fichier à sauvegarder tout ci tout ça.
            Un logiciel comme LibreOffice est critique, car il manipule des données importantes, pouvant être grosses et avoir de la valeur pour l'utilisateur.

            Un jeu de cartes sur ordinateur ou un applet qui affiche la météo n'en sont pas par exemple, s'ils crashent cela n'a aucune conséquence. Par conséquent la manière de gérer les choses n'est pas la même. Il me semble important de savoir gérer les situations suivants l'environnement et le contexte. En entreprise tu ne coderas pas les applications de la même manière si tu sais que tu vas vendre un code à plusieurs clients qu'à un seul, s'il y a du support ou non après, si c'est fait pour un avion ou seulement pour un ordinateur personnel, etc.

            Un malloc qui échoue pour distribuer un octet n'aura pas le même impact et la machine ne sera pas dans le même état qu'un malloc pour des dizaines de Mo.

            • [^] # Re: criticité

              Posté par . Évalué à  4 .

              Parce que tu sais d'habitude comment ton soft va etre utilise ?
              Prends un soft de conversion basique : il prend un fichier en entree, et en ressort un autre.
              Il ne fait que lire le fichier d'entree, resultat il ne risque pas d'endommager quoi que ce soit.
              Tu te mets a coder comme tu dis, sans verifier si les allocations reussissent, etc… parce que bon, si l'utilisateur il voit son soft exploser en vol c'est pas grave apres tout, il n'a rien perdu hein.

              Le probleme est quand ton utilisateur se met a faire des batchs de ta commande par divers moyen (shell, etc…), il n'aura aucune idee qu'il y a eu un probleme dans le fichier 468, il ne saura pas ce qui a cause le probleme, etc…

              Idem pour le gars qui se decide de creer un service de conversion sur le web, il va passer des fichiers venant de l'exterieur a ton soft de conversion, et devine quoi, ton petit soft pas important va devenir une faille de securite sur le systeme de l'utilisateur car tu ne verifies pas que tes allocations fonctionnent.

              Alors si tu parles d'un petit gadget de 100 lignes oui qui affiche l'heure, oui ca revient a taper sur un moustique avec un martinet, mais vu l'effort supplementaire que ca demande qui est minimal mieux vaut le faire parce que c'est une habitude a prendre plus qu'autre chose. Le jour ou tu te mettras a coder un truc plus gros, tu feras la chose correctement de maniere instinctive plutot qu'inserer des erreurs a tout bout de champs a cause de tes mauvaises habitudes precedentes.

              • [^] # Re: criticité

                Posté par (page perso) . Évalué à  0 .

                «Parce que tu sais d'habitude comment ton soft va etre utilise ?»

                Pour l'essentiel des softs «graphiques» j'ai quand même l'impression que c'est le cas (OK, ça peut être forké ou modifié, mais a priori si tu fais un jeu de démineur ou un coincoin< il y a peu de chances que ça devienne un soft hyper critique).

                Pis bon, oui, dans l'absolu il vaut mieux gérer des erreurs que ne pas les gérer, sauf que dans la vraie vie j'ai l'impression que pour un soft pas prévu pour ça et qui devient un truc super critique tel qu'utilisé par d'autres gens, t'en as aussi une pelletée qui sont téléchargés par deux pelés.

                (Je trouve que l'argument de «l'habitude à prendre» est plus pertinent, même si je trouve que c'est pas forcément applicable si tu codes quelques petits trucs de temps en temps sans avoir pour ambition de «coder un truc plus gros un jour».)

    • [^] # Re: criticité

      Posté par (page perso) . Évalué à  7 .

      Pour info, voici le code d'openssh [1] :

      void *
      xmalloc(size_t size)
      {
          void *ptr;
      
          if (size == 0)
              fatal("xmalloc: zero size");
          ptr = malloc(size);
          if (ptr == NULL)
              fatal("xmalloc: out of memory (allocating %lu bytes)", (u_long) size);
          return ptr;
      }
      
      [...]
      
      /* Fatal messages.  This function never returns. */
      
      void
      fatal(const char *fmt,...)
      {
          va_list args;
      
          va_start(args, fmt);
          do_log(SYSLOG_LEVEL_FATAL, fmt, args);
          va_end(args);
          cleanup_exit(255);
      }
      
      [...]
      
      /* default implementation */
      void
      cleanup_exit(int i)
      {
          _exit(i);
      }
      
      

      [1] http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/

      • [^] # Re: criticité

        Posté par (page perso) . Évalué à  5 .

        Oui, il gère en quittant le programme. Ça a du sens pour eux de le gérer ainsi. Mais ils ne continuent en laissant arrivé le SIGFAULT.

        Je pense qu'un serveur de base de données ne va pas quitter brutalement s'il n'y a pas assez de mémoire pour retourner le résultat d'une requête. Tout comme une éditeur de texte ne va pas le faire.

        Dans tous les cas, les programmes gèrent l'absence de mémoire. Ils comptent pas sur quelque chose d'externe qui pourrait les sauver.

        La "criticité" va déterminer comment tu gères ton retour NULL. Programme de moindre importance peut se contenter de faire "exit", un programme comme systemd va tenter l'impossible pour ne pas faire "exit" (enfin je l'espère).

        En C, *NULL='a'; est indéfini par la norme C89/99. Donc ça peut SEGFAULT, mais ça peut être aussi valable (x86 en mode réel) ou alors certains microcontrolleur. Mais indéfini ça veut dire que le comportement ne peut pas être prédit !

        "It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell

        • [^] # Re: criticité

          Posté par . Évalué à  0 .

          tu devrais essayer d'utiliser Word/Wordpad/MySQL quand tu as plus de mémoire.

          sinon, pour "init" (le bon vieux, bien critique)

          /*
           *  Non-failing allocation routines (init cannot fail).
           */
          static void *imalloc(size_t size)
          {
              void    *m;
          
              while ((m = malloc(size)) == NULL) {
                  initlog(L_VB, "out of memory");
                  do_sleep(5);
              }
              memset(m, 0, size);
              return m;
          }
          
          
  • # Pourquoi malloc() puis realloc() ?

    Posté par (page perso) . Évalué à  3 . Dernière modification : le 09/09/12 à 13:10

    Ton code d'exemple me chiffonne un peu: Pourquoi faire un malloc() puis un realloc() ?

    Ça peut être fait avec 2 appels à snprintf() et un seul malloc(). En effet, si tu spécifie une taille de buffer de 0 au 1er snprintf(), logiquement, il ne devrait pas chercher à remplir le buffer passé en argument. Par contre tu devrais quand même récupérer la taille finale de la chaine. Ça me semble plus court, plus lisible, et plus optimisé.

    Au final, ça donne un truc dans ce genre là:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        static const char *format = "ceci est un test, %d";
        static const int value = 42;
        int str_length;
        char *buffer;
    
        str_length = snprintf(NULL, 0, format, value);
        str_length++; /* '\0' */
    
        buffer = malloc(str_length);
        if (buffer == NULL)
        {
            fprintf(stderr, "Out of memory\n");
            return EXIT_FAILURE;
        }
        snprintf(buffer, str_length, format, value);
        printf("Out: %s\n", buffer);
        free(buffer);
    
        return EXIT_SUCCESS;
    }
    
    
    • [^] # Re: Pourquoi malloc() puis realloc() ?

      Posté par (page perso) . Évalué à  3 .

      Avant de proposer une optimisation, lire la page de manuel n'est pas du luxe :

      […]when snprintf() is called with size=0 then SUSv2 stipulates an unspecified
      return value less than 1, while C99 allows str to be NULL in this case, and
      gives the return value (as always) as the number of characters that would
      have been written in case the output string has been large enough.

      Donc 2 spécifications se contredisent sur cette fonction. Autant ne pas faire cette optimisation. De plus dans ton optimisation, tu appelles toujours deux fois snprintf et une fois malloc, alors qu'avec une valeur de départ bien choisie, tu peux être dans le cas où tu appelles le plus souvent une fois snprintf et une fois malloc et uniquement dans les cas spéciaux (voir jamais) appeler deux fois snprintf.

      "It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell

  • # fuites mémoire

    Posté par (page perso) . Évalué à  6 . Dernière modification : le 09/09/12 à 14:05

    Mon journal précédent parlait de realloc dont on ne contrôlait pas la valeur de retour. Suite à ce journal j'ai été très surpris par le nombre de commentaires clamant que ce n'était pas important, que le noyau se chargerait de tuer le processus, que le programme planterait

    Ce n'est pas ce qui a été dit dans les commentaires de ton journal précédent.

    Dans ce journal, tu prétends que ne pas tester les valeurs de retour de realloc provoque des fuites mémoire. A cela on te répond que non, puisque le processus ne survit de toutes façons pas si realloc échoue. Personne, je crois, n'a dit qu'il n'y avait pas de problème à ne pas tester les codes d'erreur de realloc (partir du principe que le programme part en SEGFAULT ce n'est pas dire qu'il n'y a pas de problème).

    Tu prends même pour exemple une application gnome qui donne l'heure et d'autres infos système, et qui occupe la mémoire de façon importante. Si tu trouves la cause de l'occupation mémoire importante, je suis prêt à parier que ce n'est pas à cause d'un realloc dont on aurait ignoré la valeur de retour.

    Une fuite mémoire c'est un tampon qui n'est jamais libéré alors qu'il aurait dû. Aucun rapport avec la choucroute ici.

    • [^] # Re: fuites mémoire

      Posté par (page perso) . Évalué à  6 .

      Dans l'exemple que j'ai pris, sur la quinzaine d'erreur, dans le journal oui en effet. On peut dire que l'exemple en question était mauvais. Mais il y'a des cas où le code fait, pratiquement, ça :

      return realloc(ptr, /* ... */);
      
      

      et la suite gère un pointeur NULL. Dans ce cas, on a des fuite, vu que si realloc échoue, ptr est perdu à jamais.

      Ensuite il y a des commentaires qui indiquent que contrôler les valeurs de retour de malloc/realloc est une perte de temps si le programme n'est pas assez critique. Ici ou dans l'autre journal. Ce qui est une aberration.

      "It was a bright cold day in April, and the clocks were striking thirteen" - Georges Orwell

      • [^] # Re: fuites mémoire

        Posté par (page perso) . Évalué à  2 .

        Dans ce cas, on a des fuite, vu que si realloc échoue, ptr est perdu à jamais

        là ok, c'est le cas typique d'appli codée avec les pieds, ou pire, par des gens qui sont passés de Java à C et qui pour qui c'est du code parfaitement acceptable.

        Ensuite il y a des commentaires qui indiquent que contrôler les valeurs de retour de malloc/realloc est une perte de temps si le programme n'est pas assez critique. Ici ou dans l'autre journal. Ce qui est une aberration.

        Je suis d'accord aussi. Diffuser du code qui ne fait pas de test d'erreurs devrait être considéré comme une très mauvaise idée. Si je considère pouvoir le faire chez moi, je m'interdis de diffuser publiquement du code qui fait ça. Donc du coup je fais du code propre systématiquement, au cas où.

        L'argument de la criticité du code a déjà été discuté, et est loin de faire l'unanimité, ce qui est une bonne chose.

        • [^] # Re: fuites mémoire

          Posté par (page perso) . Évalué à  3 .

          «Je suis d'accord aussi. Diffuser du code qui ne fait pas de test d'erreurs devrait être considéré comme une très mauvaise idée. Si je considère pouvoir le faire chez moi, je m'interdis de diffuser publiquement du code qui fait ça. Donc du coup je fais du code propre systématiquement, au cas où.»

          Si t'as les capacités de le faire, il vaut mieux le faire, OK mais d'un autre côté je trouve ça bien que des gens qui sont pas forcément capables de coder proprement (parce que c'est pas leur métier, qu'ils font juste un petit prog pour eux et se disent «ça servira peut-être à quelqu'un» et ont pas forcément l'energie d'apprendre la gestion d'erreurs de tel langage pour ça alors que ça marche) diffusent quand même ce qu'ils font, ne serait-ce que parce que des fois sur des trucs spécifiques c'est pratique d'avoir un petit outil qui servira potentiellement à deux personnes, même s'il est codé de manière hyper gruik, ne fait pas de correction d'erreur et plante de temps en temps.

          • [^] # Re: fuites mémoire

            Posté par (page perso) . Évalué à  2 .

            Je suis relativement d'accord, pour une truc à la con comme conky, franchement, on s'en fou, c'est pas un service critique…

            La question est de savoir si il existe des services critiques codés en mode on s'en fou de la gestion des erreurs, j'espère bien que non .

            • [^] # Re: fuites mémoire

              Posté par . Évalué à  6 .

              Je suis relativement d'accord, pour une truc à la con comme conky, franchement, on s'en fou, c'est pas un service critique…

              Apres avoir lu ce fil, je vais sourire quand quelqu'un resortira l'argument sur la superiorite technique du libre et la stabilite legendaire du monde linux face a windows qui fait qu'a planter.

              Linuxfr, le portail francais du logiciel libre et du neo nazisme.

  • # Simplification

    Posté par . Évalué à  3 .

    Salut,

    Je suis tout à fait d'accord sur le fait que les retours des fonctions d'allocation doivent être vérifiés. Au programmeur de décidé ensuite de l'action à effectuer en cas d'échec de ces dernières (un simple appel à exit pour un programme non critique par exemple).

    Je notes simplement, comme Jérôme Flesch, que le code pourrait se passer d'un appel à malloc en utilisant simplement une variable de type char pour le premier appel à snprintf. Je remarque également que le retour de snprintf n'est pas vérifié (il peut être négatif ou nul) et que l'espace pour le caractère de fin de chaîne a été oublié.

    {
        char tmp;
        int size = snprintf(&tmp, 1, /* ... */);
    
        if (size <= 0)
            ; /* quit */
    
        char * str = malloc(size + 1);
    
        if (str == NULL)
            ; /* quit */
    
        /* ... */
    }
    
    

    Avant de proposer une optimisation, lire la page de manuel n'est pas du luxe

    Je suis d'accord sur ce point également, mais dans ce cas ci il s'agit d'un comportement non conforme au standard donc bon, je ne me retournerais personnellement pas dessus.

    • [^] # Re: Simplification

      Posté par (page perso) . Évalué à  0 .

      il s'agit d'un comportement non conforme

      C'est conforme au standard C99 et SUSv2 ne contredit pas C99. SUSv2 dit juste qu'il n'offre pas de garantie dans ce cas, mais C99, lui, en offre.
      (Pour ceux qui auraient raté épisode précédent, vu que c'est dans un autre thread, on parle de l'appel à snprintf(NULL, 0, …))

      • [^] # Re: Simplification

        Posté par (page perso) . Évalué à  0 . Dernière modification : le 09/09/12 à 23:41

        Hm, ok, je note, ne plus lire le man et poster après 23h … les 2 standards se contredisent bel et bien …
        hmbref, +1 pour ta solution donc :)

  • # Et printf ?

    Posté par (page perso) . Évalué à  6 .

    Puisqu'on est dans de la vraie reflexion de fond, posons les bonnes questions.

    Qui vérifie les valeurs de retour de printf ? Et que faire en cas d'échec de printf ?

    En tout cas, le débat n'est pas inintéressant. Je rejoins PbPg sur le fait que faire un bon test et chaînage d'erreur est la base d'une bonne bibliothèque. Je le fais quand je développe des libs à usage externe. Et une des règles de base est d'ailleurs : ne jamais retourner true quand une fonction réussi. On retourne toujours 0 quand ça réussit de façon à pouvoir glisser dans le futur tous les cas d'erreurs nouveaux.

    Ça suppose quand même d'avoir une grande base de code d'erreurs unifiés pour permettre la propagation assez simplement. Ça veut dire aussi qu'il faut convertir tous les cas d'erreurs des libs externes qu'on appelle. Ça peut vite être assez lourd. Mais très bien fait, ça permet d'identifier assez précisément une erreur quand une application se crash. C'est la différence entre "ah tiens mon application carte à puce crash" et "ah tiens, le driver propriétaire de mon lecteur de carte à puce n'arrive pas à faire de changement de vitesse de communication avec ma carte à puce", ce qui est tout de suite beaucoup plus utile.

    Cela étant dit, je tends plutôt vers une approche à la Zenitram: au quotidien, très peu de soft que j'écris est critique, on est soit dans la ligne de commande, soit dans l'appli graphique à deux balles. Et donc la gestion de ce genre d'erreur me passe un peu au dessus. Côté diagnostic, j'ai plus tendance à m'appuyer sur du logging bien foutu que sur l'erreur elle-même retournée. Il est vrai que je fais beaucoup de python ou une bonne stacktrace est quand même sacrément claire!

    • [^] # Re: Et printf ?

      Posté par . Évalué à  3 .

      Ça suppose quand même d'avoir une grande base de code d'erreurs unifiés pour permettre la propagation assez simplement

      Ca existe deja, suffit de reutiliser(Win32 en contient des centaines par exemple).

      Ça veut dire aussi qu'il faut convertir tous les cas d'erreurs des libs externes qu'on appelle.

      Oui et non, ca depend de la granularite des erreurs que tu veux gerer, et sous Windows au moins c'est souvent transparent vu qu'il y a "une" liste standard d'erreurs.

      Cela étant dit, je tends plutôt vers une approche à la Zenitram: au quotidien, très peu de soft que j'écris est critique, on est soit dans la ligne de commande, soit dans l'appli graphique à deux balles. Et donc la gestion de ce genre d'erreur me passe un peu au dessus.

      Je veux bien que ce soit pas critique dans ce cas, mais faire cela :
      a) T'empeche de reutiliser ce que tu as ecrit dans quelque chose de plus important
      b) Te donne des mauvaises habitudes je dirais, qui rende l'ecriture de code "critique" plus risque

      Mon point de vue est que ces checks sont de toute facon tres rapide a ajouter(il y a quasiment toujours des chemins d'erreurs pour d'autres raisons, suffit donc de les reutiliser) et qu'au final cela coute tellement peu a faire qu'il vaut mieux le faire systematiquement.

      • [^] # Re: Et printf ?

        Posté par (page perso) . Évalué à  2 .

        Ça suppose quand même d'avoir une grande base de code d'erreurs unifiés pour permettre la propagation assez simplement

        Ca existe deja, suffit de reutiliser(Win32 en contient des centaines par exemple).

        C'est vrai et c'est assez appréciable sous Win32. Mais c'est pas utilisable quand on fait un soft portable. Et c'est difficilement utilisable quand tu fais un soft qui assemble plusieurs bibliothèques ensemble. Voici donc deux cas, où la gestion d'erreur propre demande de développer son propre gestionnaire de codes d'erreur et de message.

        Mon point de vue est que ces checks sont de toute facon tres rapide a ajoute

        Oui pour un truc très simple, si l'application est structurée de cette façon. Non si tu veux faire un truc un minimum intelligent et détaillé, pour par exemple proposer une solution à quelques erreurs courantes.

        L'exemple de la partition où tu peux pas écrire mais où tu veux sauver ton document est intéressant à traiter…

        • [^] # Re: Et printf ?

          Posté par . Évalué à  4 .

          Sauf que ce que je ne pige pas dans tout ça, c'est qu'au bout d'un moment, tout le monde finit par développer (ou réutiliser) un ensemble de fonctions utilitaires qui justement enrobent les fonctions utiles (donc malloc, free, avec une fonction genre fatal ou crash qui logge un message d'erreur et sort…).

          Si ton soft n'est pas critique, il suffit de les utiliser pour que ça plante « proprement ». Sinon, de toute manière il faudra développer ou réutiliser du code plus robuste. Je pense que ce qu'Étienne veut faire passer comme message, c'est qu'il n'est quand même pas sorcier de faire des fonctions « wrapper » pour ce qui est utilisé souvent, et ainsi faire une gestion minimale des erreurs.

          J'ai raté quelque chose ?

  • # Fuite mémoire

    Posté par . Évalué à  6 .

    J'ai fait un patch pour ce que je pouvais, rapidement, corriger. VmPeak, sur la configuration par défaut, passe de 317916 kB à 211388 kB et VmData de 189372 kB à 123836 kB. Une amélioration notable après environ 4 ou 5 realloc corrigés.

    Tu as l'air de penser que c'est les corrections sur les realloc qui ont réduit l'occupation mémoire. J'en doute très fortement.

    Tes corrections, ça permet d'éviter une fuite sur une situation extrêmement rare. Un malloc ou realloc qui échoue, c'est un cas réellement exceptionnel. Si tu veux tester ça, remplacer les reallocs par un petit wrapper qui log les cas où ça retourne NULL. Je suis prêt à parier que tu n'en verras aucun.

    À mon avis, le gain de mémoire que tu constates, ça doit venir d'options de compilations différentes.

    • [^] # Re: Fuite mémoire

      Posté par . Évalué à  1 .

      D'un coté il y a la bonne méthode, de l'autre … l'autre.

      il me serait pas venu à l'idée de mesurer un delta en consommation mémoire en regardant VmPeak et encore moins VmSize, sur de si petites modifications (je dis pas que c'est dénué de sens, c'est juste comme mesurer le niveau d'un bassin pour compter les poissons qu'il y a dedans alors qu'on les connait tous par leur nom).

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.