IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Delphi Discussion :

BUG dans le langage Delphi


Sujet :

Langage Delphi

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut BUG dans le langage Delphi
    Ce n'est pas mon genre de crier au bug dans un langage de programmation et même j'ai plutôt tendance à me méfier des personnes qui font habituellement ce genre de déclaration (car dans 99% des cas c'est plutôt de la mauvaise programmation), mais dans mon cas et après avoir passé une après-midi à chercher un bug dans un code (qui il faut le dire est assez complexe) j'en ai conclu qu'il y a un problème dans la gestion des 'string' dans Delphi.
    Pour m'en assurer j'ai fait le test suivant (nouveau projet vierge, un simple bouton 'Button1' sur la fiche, ajouter un gestionnaire sur le click du bouton puis copier/coller le code ci-dessous) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
     
    unit Unit1;
     
    interface
     
     
    uses Forms, Classes, Controls, StdCtrls, Dialogs;
     
    type
      TTestEvent = procedure ( const Value: string ) of object;
     
      TTest = class
      public
         procedure Show( const AText: string;
                         SetParentText: TTestEvent );
      end;
     
      TForm1 = class(TForm)
          Button1: TButton;
          procedure FormCreate(Sender: TObject);
          procedure FormDestroy(Sender: TObject);
          procedure Button1Click(Sender: TObject);
      private
         FText: string;
         FTest: TTest;
      public
         procedure TestShow;
         procedure SetText( const Value: string );
      end;
     
     
    var
      Form1: TForm1;
     
    implementation
     
    {$R *.dfm}
     
     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
       FTest:= TTest.Create;
    end;
     
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
       FTest.Free;
    end;
     
    procedure TForm1.TestShow;
    begin
       SetText( 'Message1' );
       FTest.Show( FText, SetText );
    end;
     
    { TTest }
    procedure TTest.Show( const AText: string; SetParentText: TTestEvent);
    begin
       ShowMessage( AText );
       SetParentText( 'Message2' );
       ShowMessage( AText );
    end;
     
    procedure TForm1.Button1Click(Sender: TObject);
    begin
       TestShow;
    end;
     
    procedure TForm1.SetText(const Value: string);
    begin
       FText:= Value;
    end;
     
    end.

    En fait on pourrait simplifier le problème en disant que cela vient du fait que le compilateur envoie un pointeur du string lorsqu'on précise 'const', seulement voilà d'une part le développeur n'est pas censé le savoir, d'autre part avec les compteurs de référence Delphi devrait savoir qu'il ne faut pas écraser le contenu de l'ancienne chaîne de caractères lors de la seconde affectation car il reste une référence dessus. Hors là il "libère" la mémoire ce qui fait que si vous exécutez ce test vous ne verrez pas "Message1" puis "Message1" (et non "Message2") comme cela devrait être le cas mais "Message1" et puis le contenu de la même zone mémoire invalidée (ce qui dans mon cas sous Delphi 7 correspondant à "Project1", le nom de mon projet), ou mieux une violation d'accès ...
    Alors BUG ou pas BUG ?? pour l'instant et jusqu'à ce qu'on me prouve le contraire c'est un BUG. Un argument de type 'const' ne peut-être modifié dans l'ensemble d'un "scope" et là ce n'est pas le cas.

    Ce qui m'étonne encore plus c'est que Delphi alloue une nouvelle zone mémoire au pointeur alors que la nouvelle chaîne à la même taille (je m'attendais d'abord à avoir "Message2"...), donc cela laisse à penser qu'il a détecté que l'ancien zone mémoire est toujours référencé, mais le contenu de l'ancienne est visiblement "invalidé" car il l'utilise de suite pour y stocker d'autres infos, enfin bref... je suis vert, moi qui préconisait à tout le monde dans ma boite de toujours utiliser les 'const' pour les 'string', 'record' et 'array' utilisés comme argument, ça remet tout en cause !!!
    Pour le moment j'ai corrigé un problème d'erreur "violation d'accès" ou "erreur interne" aléatoire juste en supprimant 'const' dans mon prototype de fonction et je trouve cela absoluement anormal !

    Peut-être quelqu'un pourra-t-il me rassurer en m'indiquant que j'ai fait quelque chose de mal, qu'il y a un correctif, ou une autre façon de faire (sans se passer du 'const' qu'il doit y avoir en 10000000 d'exemplaire dans mon projet depuis le temps que j'ai passé la consigne) ?

    Merci de votre aide, si aide possible il y a (sinon merci de votre compassion) car au vue de ce problème je pourrai simplement passer mon string sans préciser 'const' devant bien sûr, mais dans ce cas comment savoir si je ne vais pas avoir le bug ailleurs ? Autant supprimer tous les 'const', et ralentir atrocement certaines fonctions récursives qui avaient gagné beaucoup avec ce fameux 'const' ??? Sans compter que j'en dormirai plus...

    [Edit] en passant 'var' plutôt que 'const' je n'ai plus de messages d'erreur mais à la place mes chaînes de caractères constantes voir leur contenu modifié dans le même scope, c'est toujours pas logique mais au moins le Delphi va réallouer le pointeur plutôt que d'en allouer un nouveau (et que l'ancien pointe sur une zone mémoire plus allouée)... Hum je me demande bien ce que le Delphi fout en interne avec sa gestion de chaînes de caractères, quelqu'un pourrait-il m'expliquer ?

  2. #2
    Membre éprouvé Avatar de Yurck
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2005
    Messages
    682
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 15
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2005
    Messages : 682
    Points : 912
    Points
    912
    Par défaut
    En effet il s'agit d'un bogue.

    Tu ne peux pas garder tes problèmes pour toi !!!!!

    Me revoilà obliger (déontologie oblige) de revérifier la cinématique de l'ensemble des processus récursifs de mes applis.

    Et un vendredi soir à 17h30 !!!!

    "DVP Nova, les développeurs ne lui disent pas merci"

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par Yurck Voir le message
    En effet il s'agit d'un bogue.

    Tu ne peux pas garder tes problèmes pour toi !!!!!

    Me revoilà obliger (déontologie oblige) de revérifier la cinématique de l'ensemble des processus récursifs de mes applis.

    Et un vendredi soir à 17h30 !!!!

    "DVP Nova, les développeurs ne lui disent pas merci"
    Merci pour ta compassion, et désolé pour toi si tu as eu le "malheur" d'associer traitements récursifs et 'const' (ça devrait être précisé dans l'aide qu'il faut pas !), honnêtement j'ai bien trop la flemme de faire de même pour moi, il faut dire que j'y passerai plusieurs jours sans être sûr d'avoir tout vérifié !


    En fait je pense qu'on peut généraliser le problème et trouver un remède en évitant de passer des paramètres de type non ordinal (string, array, record) en const lors de traitements récursifs sauf que justement les const sont souvent intéressants dans les traitements récursifs alors c'est zuber... enfin c'est quand même limité à un "certain modèle" de schéma récursif (qui est suceptible de modifier les données de l'appelant de la méthode)

  4. #4
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    A mon sens il serait normal d'obtenir 'Message1" puis "Message2" et non "Message1" et "Message1" comme tu le dis...
    Bien sur tu as passé le paramètre en const, ce qui empêche le compilo de le modifier dans la fonction, mais là forcément c'est ta fonction SetParentText qui va modifier directement la chaine sans que le compilo s'en rende compte.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
       ShowMessage( AText );
       SetParentText( 'Message2' );
       ShowMessage( AText );
    Disons que ce problème "ne me choque pas". Par contre, d'après ce que tu décris tu n'obtiens pas "Message2" et là c'est plutot bizarre...

  5. #5
    Membre éprouvé Avatar de Yurck
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2005
    Messages
    682
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 15
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2005
    Messages : 682
    Points : 912
    Points
    912
    Par défaut
    Citation Envoyé par ZZZzzz2 Voir le message
    le "malheur" d'associer traitements récursifs et 'const'
    Et ben non je n'ai pas fait de mauvaises combinaisons dans les secteurs critiques, on ne peut pas toutes les faire (les conn..).

    Pour ma part les traitements récursifs génériques sont localisés dans trois unités seulement, ces unités s'enrichissent et sont communes à l'ensemble de mes applications delphi de ces 7 dernières années.
    Je peux donc dormir à 99% tranquille.

    C'est bon je suis clean.

    Bon we.

  6. #6
    Membre éprouvé Avatar de Yurck
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2005
    Messages
    682
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 15
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Février 2005
    Messages : 682
    Points : 912
    Points
    912
    Par défaut
    Citation Envoyé par Stef_D Voir le message
    Disons que ce problème "ne me choque pas". Par contre, d'après ce que tu décris tu n'obtiens pas "Message2" et là c'est plutot bizarre...
    Ne pas être choqué soit, mais je dis que c'est chiant et merdique car on devrait afficher sinon message1 au moins message2 et trouver cela bizarre c'est un peu gentil, NON ?

    Enfin il faudrait refaire le test avec Delphi 6 ou moins par curiosité.

  7. #7
    Nouveau Candidat au Club
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1
    Points : 1
    Points
    1
    Par défaut
    Formidable !

    bug à remonter à la team Delphi alors ?

  8. #8
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par Stef_D Voir le message
    A mon sens il serait normal d'obtenir 'Message1" puis "Message2" et non "Message1" et "Message1" comme tu le dis...
    Bien sur tu as passé le paramètre en const, ce qui empêche le compilo de le modifier dans la fonction, mais là forcément c'est ta fonction SetParentText qui va modifier directement la chaine sans que le compilo s'en rende compte.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
       ShowMessage( AText );
       SetParentText( 'Message2' );
       ShowMessage( AText );
    Disons que ce problème "ne me choque pas". Par contre, d'après ce que tu décris tu n'obtiens pas "Message2" et là c'est plutot bizarre...

    Non mais c'est CLAIREMENT CHOQUANT ce comportement ! ça devrait afficher "Message1" point ! et si je passe en 'var' alors là effectivement ça peut afficher "Message2" ça reste pseudo logique puisque le delphi précise dans l'aide que dans ce cas la variable est passé par adresse, que la méthode peut modifier le contenu de la variable, etc, etc...

    Quand on passe un 'const' on s'attend à ce que la variable ne change pas son contenu, c'est la moindre des choses, si en plus son contenu devient invalide c'est encore pire (mais cela signifie que Delphi a fait la moitié du traitement : il a allouée une nouvelle zone mémoire sur le changement de texte car il a du détecté que l'ancienne zone devait être gardé. Mais dans un second temps il a marqué cette zone comme libre... et là je comprend pas pourquoi et c'est là à mon avis qu'il y a un bug). Moi j'ai juste testéé avec Delphi7 et 2007

  9. #9
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par Fablaloz Voir le message
    Formidable !

    bug à remonter à la team Delphi alors ?
    Bah je suis surement pas le premier à l'avoir trouvé, surtout que c'est dans Delphi7, il doit-être connu depuis un moment. reste à savoir s'il y a un correctif.

  10. #10
    Expert confirmé
    Avatar de anapurna
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2002
    Messages
    3 434
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mai 2002
    Messages : 3 434
    Points : 5 846
    Points
    5 846
    Par défaut
    salut

    le problème est effectif avec un string, un objet ou un pointer
    un test simple est de remplacer le string par un shortString et la tout revient dans les normes

    je me souvient d'un problème similaire avec un fonction dans lequel tu passe un
    string et que l'on oubli d'initialisé

    @+ Phil

  11. #11
    Membre confirmé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Points : 546
    Points
    546
    Par défaut
    Bonjour,
    Citation Envoyé par ZZZzzz2 Voir le message
    Non mais c'est CLAIREMENT CHOQUANT ce comportement ! ça devrait afficher "Message1" point ! [...]Quand on passe un 'const' on s'attend à ce que la variable ne change pas son contenu, [...]
    Personnellement, je trouve que le comportement normal serait plutôt d'obtenir "Message1" puis "Message2" ; const signifie que la variable ne peut être modifié par la routine
    Les paramètres constantes sont semblables aux paramètres par valeur à cette différence qu'il n'est pas possible d'affecter une valeur à un paramètre constante dans le corps de la routine, ni de le transmettre comme paramètre var à une autre routine.
    et non leur valeur ne peut changer . C'est pareil dans d'autres langages : exemple C++ (const par ref)
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void Test( const string & const_str, string & str )
    {
         cout << const_str << endl;
         str = "2";
         cout << const_str << endl;
    }
     
    int _tmain(int argc, _TCHAR* argv[])
    {
         string str = "1";
         Test( str, str );
         getchar();
         return 0;
    }
    affiche 1 puis 2.
    En revanche le fait de ne pas obtenir "Message2" est un phénomène ... toujours présent en Turbo Delphi 2006
    (j'ai simplifié le cas de test)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    unit Unit1;
     
    interface
     
    uses
      Windows, StdCtrls, Classes, Controls, Forms, Dialogs;
     
    type
      TTestEvent = procedure ( const Value: string ) of object;
     
      TForm1 = class(TForm)
        Button1: TButton;
          procedure Button1Click(Sender: TObject);
      private
         FText: string;
      end;
     
    var
      Form1: TForm1;
     
    implementation
    {$R *.dfm}
    procedure Test_Show( const AText: string; var VarText : string);
    begin
       ShowMessage( AText );
       VarText := 'Message2';
       ShowMessage( AText ); // Affiche "r" (mémoire réalouée)
    end;
     
    procedure TForm1.Button1Click(Sender: TObject);
    begin
       FText := 'Message1';
       Test_Show( FText, FText );
    end;
     
    end.
    Le compilateur pousse directement le pointeur de la chaîne (sans incrémenter son compteur de référence), il faut donc s'assurer qu'il reste valide durant toute l'éxécution de la méthode (comme pour la méthode c_str() des string C++ pour continuer l'analogie).
    D'une manière générale il faut éviter (voire même bannir) les effets de bords avec les const.
    Pour la blague, depuis Delphi 2006 les records peuvent posséder des méthodes, qui peuvent modifier les données, restant invoquables même sur des paramètres const ; selon leurs tailles ils peuvent être passé par valeur ou référence, ce qui entraine ou non la modification de la varibale d'origine .

  12. #12
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    Citation Envoyé par gb_68 Voir le message
    Bonjour,Personnellement, je trouve que le comportement normal serait plutôt d'obtenir "Message1" puis "Message2" ; const signifie que la variable ne peut être modifié par la routine et non leur valeur ne peut changer . C'est pareil dans d'autres langages : exemple C++ (const par ref)
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void Test( const string & const_str, string & str )
    {
         cout << const_str << endl;
         str = "2";
         cout << const_str << endl;
    }
     
    int _tmain(int argc, _TCHAR* argv[])
    {
         string str = "1";
         Test( str, str );
         getchar();
         return 0;
    }
    affiche 1 puis 2.
    En revanche le fait de ne pas obtenir "Message2" est un phénomène ... toujours présent en Turbo Delphi 2006
    (j'ai simplifié le cas de test)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    unit Unit1;
     
    interface
     
    uses
      Windows, StdCtrls, Classes, Controls, Forms, Dialogs;
     
    type
      TTestEvent = procedure ( const Value: string ) of object;
     
      TForm1 = class(TForm)
        Button1: TButton;
          procedure Button1Click(Sender: TObject);
      private
         FText: string;
      end;
     
    var
      Form1: TForm1;
     
    implementation
    {$R *.dfm}
    procedure Test_Show( const AText: string; var VarText : string);
    begin
       ShowMessage( AText );
       VarText := 'Message2';
       ShowMessage( AText ); // Affiche "r" (mémoire réalouée)
    end;
     
    procedure TForm1.Button1Click(Sender: TObject);
    begin
       FText := 'Message1';
       Test_Show( FText, FText );
    end;
     
    end.
    Le compilateur pousse directement le pointeur de la chaîne (sans incrémenter son compteur de référence), il faut donc s'assurer qu'il reste valide durant toute l'éxécution de la méthode (comme pour la méthode c_str() des string C++ pour continuer l'analogie).
    D'une manière générale il faut éviter (voire même bannir) les effets de bords avec les const.
    Pour la blague, depuis Delphi 2006 les records peuvent posséder des méthodes, qui peuvent modifier les données, restant invoquables même sur des paramètres const ; selon leurs tailles ils peuvent être passé par valeur ou référence, ce qui entraine ou non la modification de la varibale d'origine .

    C'est une façon de voir les choses, disons que pour le dev. "avertit" qui connait un minimum le fonctionnement interne sait qu'un const équivaut à un passage par pointeur alors effectivement le résultat attendu est 'Message2' mais pour un dev. "lambda" qui ne connait pas le fonctionnement interne du compilateur (et je serai tenté de dire qu'à la limite il ne devrait pas avoir à le connaitre) ce n'est pas logique. Pour simplifier mon point de vue de dev. "lambda" : la variable est passée comme constante en argument, durant le "scope" de la fonction son contenu ne devrait pas pouvoir bouger puisqu'en l'occurence je n'utilise aucun pointeur dans mon implémentation. Le fait de passer l'argument sans préciser 'const' "corrige" le problème, alors que sémantiquement et d'un point de vue algorithmique cela n'a aucun rapport...
    Je pense que le compilateur devrait être capable de "savoir" qu'on utilise une référence comme 'const' qui ne doit donc pas pouvoir être modifié, du coup quand on modifie le contenu de la chaine il alloue un nouveau pointeur et garde l'ancien. D'ailleurs pourquoi alloue -t-il un nouveau pointeur plutôt que de réutiliser l'ancien avec 'const' et non avec 'var' ? N'est-ce pas là le problème ? Quoiqu'il en soit c'est chiant de découvrir qu'il peut y avoir ces effets de bord avec l'utilisation du 'const'.

    En tout cas ton exemple expose de manière plus simple le problème que j'ai mis en évidence. Ce qui me surprend c'est qu'à priori ce phénomène semble connu (vu comme tu en parles), alors pourquoi cela n'est pas corrigé à l'heure actuelle ? Le problème que tu évoques avec Delphi2006 est très éloquent (mais au moins n'entraine pas des violations d'acces).

  13. #13
    Membre habitué Avatar de phplive
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    179
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 179
    Points : 150
    Points
    150
    Par défaut
    Bjr

    Non ce n'est pas un bug mais un effet de bord dû à une mauvaise utilisation de const ...

    J'en sais quelque chose j'ai été confronté à un cas de figure similaire avec les pointeurs d'interfaces ...

    Ma conclusion avec const : une fonction ne doit pas modifier directement une variable passée par const (ça le compilo l'interdit normalement)
    ni indirectement (ça le compilo ne peut pas l'empêcher) en utilisant un pointeur ou via l'appel à une autre fonction.

    Bref c'est bien toute la pile des appels qui doit être vérifiée et pas uniquement la première fonction/procédure/méthode appelée.


    D'abord il faut bien voir que const ou var c'est la même chose : c'est bien l'adresse de la variable qui est passée sauf
    qu'avec const il n'y a pas d'incrémentation du compteur de références lors de l'appel ni donc de décrémentation lors du retour.
    C'est pourquoi avec const l'exécution de la fonction est plus rapide.



    Maintenant d'où vient le problème ?

    Tout simplement de ta fonction Test.Show() qui modifie indirectement le contenu d'une variable passée avec const !
    Effet de bord garanti la violation d'accès n'est pas loin !


    Explications qui valent ce qu'elles valent hein :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    procedure TForm1.TestShow;
    begin
      // Avant FText contient une chaîne vide il pointe vers nil : FText->nil
      SetText( 'Message1' );   
      |
      |   
      +--> procedure TForm1.SetText(const Value: string);  
           begin
    	  // Value pointe vers A qui contient 'Message1' : Value->A 
              // Value est considéré comme une chaîne courte (optimisation
              // du compilateur ?) et n'utilise pas le comptage de réf ce qui
              // n'arrange pas les choses
     
    	  FText:= Value; 
              // FText->B(1) avec B contenant une copie de A
    	end;
     
      // Maintenant FText->B(1) : FText pointe vers une structure (un objet ?) 
      // string que j'appelle B contenant 'Message1' et avec le cpt de réf à 1
     
     
      // Appel à la fonction qui pose problème
      FTest.Show( FText, SetText );
      |
      |
      +--> procedure TTest.Show( const AText : string; SetParentText: TTestEvent);  
           begin
    	 // AText = FText donc AText->B(1) B contient 'Message1'
             // le compteur de réf est tjrs à 1 du fait de const
     
             ShowMessage(AText);           // aucun pb affiche 'Message1'
             // On a encore AText->B(1)  
     
     
             // Sacrilège on va modifier FText alors que AText pointe sur
             // la même adresse sachant qu'on a bypassé le mécanisme de
             // comptage de références ! Continuons on verra bien ...
             SetParentText( 'Message2' );  
             |
             |   
             +--> procedure TForm1.SetText(const Value: string);  
                  begin
    	        // Value pointe vers C qui contient 'Message2' : Value->C
                    // FText->B(1) 
     
                    // Le contenu de FText est librement modifiable 
                    // Mais attention on a aussi AText->B(1) !!!!
        	        FText:= Value; 
                    // FText->D(1) avec D contenant une copie de C 
                    // càd 'Message2'
                    // Le compteur de B passe à zéro l'ancienne variable 
                    // pointée par FText est détruite ! B n'existe plus
                    // Pas de chance on a toujours AText->B(0) aie !!!!
     
    	      end;
     
     
       	 // Au retour
             // FText->D(1) et vaut 'Message2'
             // AText->B(0) donc pointe sur une zone qui n'est plus valide
     
     
             ShowMessage( AText );  // Violation d'accès ou effet de bord ...
           end;
    end;
    Moralité : faut faire hyper gaffe !

  14. #14
    Nouveau membre du Club
    Profil pro
    Inscrit en
    Juin 2005
    Messages
    68
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2005
    Messages : 68
    Points : 32
    Points
    32
    Par défaut
    Ma conclusion avec const : une fonction ne doit pas modifier directement une variable passée par const (ça le compilo l'interdit normalement)
    ni indirectement (ça le compilo ne peut pas l'empêcher) en utilisant un pointeur ou via l'appel à une autre fonction.

    Bref c'est bien toute la pile des appels qui doit être vérifiée et pas uniquement la première fonction/procédure/méthode appelée.
    ...
    Je suis d'accord pour dire qu'obtenir 'Message1' puis 'Message2' peut être normal puisqu'effectivement si tu modifies la variable via une fonction ou/et par référence, le compilo aura bien du mal à le détecter... bref. C'est au codeur de faire attention. Par contre je ne comprend pas en quoi obtenir une violation d'accés pourrait être assimilé à un comportement "normal". A mon sens Delphi s'embrouille avec ses compteurs de référence : si on modifie la chaine par référence à l'intérieur d'une fonction où elle est passée en 'const' alors son compteur devrait être correctement mis à jour... Delphi fait des "optimisations" qui peuvent provoquer des incohérences et donc au final c'est plutot un bug du compilo dans ce cas précis.

  15. #15
    Membre habitué Avatar de phplive
    Profil pro
    Inscrit en
    Avril 2003
    Messages
    179
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2003
    Messages : 179
    Points : 150
    Points
    150
    Par défaut
    Citation Envoyé par Stef_D Voir le message
    si on modifie la chaine par référence à l'intérieur d'une fonction où elle est passée en 'const' alors son compteur devrait être correctement mis à jour...
    Heu ???? Non, non non ! On ne doit pas le faire. On utilise pas const uniquement parce qu'on s'aperçoit que ça va plus vite avec que sans en espèrant que si on modifie qd même la variable Delphi rattrapera le tir.

    Si le programmeur décide d'utiliser const c'est parce que justement il sait que sa chaîne ne sera pas mise à jour
    Comme son nom l'indique elle ne doit pas être modifiée sinon ce n'est plus une constante !

    Si la chaîne est succeptible d'être modifiée il faut utiliser var.

    A mon sens Delphi s'embrouille avec ses compteurs de référence
    Non ! Car const désactive la gestion du comptage de références.
    Aucune embrouille il ne s'en sert tout simplement pas ... il fait exactement ce qu'on lui demande.
    C'est au programmeur de savoir ce qu'il fait

    je ne comprend pas en quoi obtenir une violation d'accés pourrait être assimilé à un comportement "normal".
    Nous sommes d'accord Ce n'est pas normal puisque c'est le résultat d'une erreur du programmeur. Ce n'est pas logique de vouloir modifier, même involontairement, une constante d'où violation d'accès "potentielle".
    Maintenant c'est vrai que de temps en temps cet effet de bord est difficile à détecter.

  16. #16
    Membre éprouvé
    Avatar de CapJack
    Homme Profil pro
    Prof, développeur amateur vaguement éclairé...
    Inscrit en
    Mars 2004
    Messages
    624
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Prof, développeur amateur vaguement éclairé...
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2004
    Messages : 624
    Points : 988
    Points
    988
    Par défaut
    Je suis assez d'accord avec phplive. On aurait le même problème avec un objet, étant donné que les objets sont implicitement transmis comme des pointeurs. Dans ce cas, le paramètre const interdit de changer d'instance d'objet dans le corps de la procédure, mais pas de modifier les propriétés de l'objet transmis !

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
      TTest =class
      private
       FValue : Integer;
      public
       property Value : Integer read FValue write FValue;
      end;
     
    ...
     
    procedure ChangeValue(const T : TTest;const AValue : Integer);
     begin
       T.Value := AValue;
     end;
     
    procedure TForm1.Button1Click(Sender: TObject);
    begin
     FTest.Value := 2532;
     ShowMessage(IntToStr(FTest.Value));
     ChangeValue(FTest,14785);
     ShowMessage(IntToStr(FTest.Value));
    end;
    Renvoie bien 2532, puis 14785. Par contre une affectation T := sera rejetée par le compilateur. Ainsi on ne peut pas changer d'instance, mais rien n'interdit de modifier les propriétés de l'instance transmise.

    Ce n'est pas un bug, c'est lié au fait que les objets pointés sont explicitement des pointeurs, et la documentation du langage est claire à ce sujet. Extrait de la doc de Delphi :

    Paramètres constantes
    Un paramètre constante (const) est semblable à une constante locale ou à une variable en lecture seule. Les paramètres constantes sont semblables aux paramètres par valeur à cette différence qu'il n'est pas possible d'affecter une valeur à un paramètre constante dans le corps de la routine, ni de le transmettre comme paramètre var à une autre routine. Par contre, si vous transmettez une référence d'objet comme paramètre constante, il est quand même possible de modifier la valeur des propriétés de l'objet.

    L'utilisation de const permet au compilateur d'optimiser le code pour les paramètres de type structuré ou chaîne. Cela évite également de transmettre involontairement par adresse le paramètre à une autre routine.

    Voici, par exemple, l'en-tête de la fonction CompareStr de l'unité SysUtils :

    function CompareStr(const S1, S2: string): Integer;
    Comme S1 et S2 ne sont pas modifiés dans le corps de CompareStr, ils peuvent être déclarés comme paramètres constantes.
    Il convient de remarquer plusieurs choses : d'une part ces dangers potentiels viennent de choix explicitement faits par Borland lors du passage du langage Pascal au langage "Delphi", choix dictés plus par l'efficacité du code et la facilité de codage que par le respect de la sémantique du Pascal originel.

    Après tout, même un passage de paramètre objet sans const ni var, devrait conformément à la logique du Pascal générer une copie de l'instance d'objet sur la pile, de la même façon qui record est copié sur la pile ! Or, regardez ce que donne une modification d'un tel paramètre (Free, puis Create)... la fois où j'ai osé critiqué ces choix, je me suis fait renvoyer dans les cordes par certains, alors maintenant je me tais, mais je persiste dans mes idées. Dans le même temps, je comprends aussi que la performance soit un critère de choix comme un autre.

    S'agissant du type string, ce qui est choquant c'est que ce type, lui aussi créé pour des raisons de facilité, est censé se comporter de la même façon qu'une chaîne courte, et ne le fait pas. Mais fondamentalement, c'est le même problème. Car en fait, ce n'est pas un pointeur vers le paquet de caractères qui est transmis, mais un pointeur vers une structure contenant elle-même le pointeur vers les données, ainsi que le compteur de référence. Donc, on peut parfaitement modifier et l'un, et l'autre.

    Ce qu'il serait intéressant de vérifier, c'est si le compteur de référence de l'ancienne chaîne est bien remis à zéro (un peu d'assembleur, peut-être). Si non, c'est un bug. Si oui (ce que je pense), le garbage collector fait parfaitement son travail, il n'y a aucun bug, et le problème vient, encore et toujours, de l'implicite du langage Delphi. Chaînes et objets se comportent implicitement comme le feraient des pointeurs vers une structure : on ne peut pas modifier le pointeur, mais la structure, si.

    Donc, un développeur d'application n'est peut-être pas censé savoir tout ce que bricole le compilateur, certes, mais dans Delphi, il est préférable qu'il ait bien conscience de tout ce qu'il y a de sous-entendu dans le langage.

    Conclusion : méfiez-vous des paramètres, const ou pas, qui sont implicitement des pointeurs. Nous avons parlé des objets et des chaînes, mais peut-être y en a-t-il d'autres auxquels je ne pense pas.

  17. #17
    Membre du Club
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    149
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 149
    Points : 61
    Points
    61
    Par défaut
    phplive>

    Merci pour ton explication même si personnellement j'avais déjà à peu près compris ce qu'il se passait sauf peut-être au niveau des compteurs de référence justement, en effet je trouve que désactiver le compteur de référence en utilisant un 'const' n'est pas forcément logique. En fait comment justifies-tu au développeur que le bug est corrigé si on retire le mot clé "const", d'un point de vue algorithmique il n'y a aucune logique.
    En fait pour moi c'est simple, les arguments ont pour durée le scope de la fonction, donc durant ce temps le compilateur doit garder une référence ou tout autre type de lien sur ces données. une information qui dit "garde ces données" (ici le contenu de "Message1") et à priori ce n'est pas le cas. C'est un peu décevant, l'aide devrait au moins expliquer ces effets de bord possibles.

    CapJack>
    Oui mais justement dans le cas des objets la doc. est très explicite au sujet du fait qu'on peut toujours modifier les propriétés, et on le comprend très bien en ce sens que les objets sont toujours passés par pointeur dans le Delphi (ce qui explique qu'il n'y a jamais de copie d'objet par une simple affectation, et que nombre d'objet ont une méthode 'Assign'). Et puis le problème se pose pas du tout avec les objets, qui sont alloués et détruit par le développeur, à l'inverse justement des string, qui sont des objets dynamiques gérés par le compilateur lui-même. Là je comprend moins bien qu'on soit confronté à ce type de problème. Enfin j'ai déjà donné mon point de vue, je pense en fait que cela aurait pu être faisable d'éviter cet effet de bord franchement pénible (c'est chiant que ce soit au développeur de faire gaffe à ça).

  18. #18
    Expert éminent sénior

    Avatar de sjrd
    Homme Profil pro
    Directeur de projet
    Inscrit en
    Juin 2004
    Messages
    4 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : Suisse

    Informations professionnelles :
    Activité : Directeur de projet
    Secteur : Enseignement

    Informations forums :
    Inscription : Juin 2004
    Messages : 4 517
    Points : 10 154
    Points
    10 154
    Par défaut
    Au fait, je vois ce thread juste maintenant. Mais tout est expliqué et décortiqué là :
    Pourquoi un paramètre const change-t-il mystérieusement de valeur ?

  19. #19
    Aos
    Aos est déconnecté
    Membre habitué

    Profil pro
    Inscrit en
    Janvier 2006
    Messages
    189
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Janvier 2006
    Messages : 189
    Points : 187
    Points
    187
    Par défaut
    lol srjd, au moins c'est reglé

  20. #20
    Expert éminent sénior
    Avatar de Paul TOTH
    Homme Profil pro
    Freelance
    Inscrit en
    Novembre 2002
    Messages
    8 964
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 55
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Freelance
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Novembre 2002
    Messages : 8 964
    Points : 28 448
    Points
    28 448
    Par défaut
    Citation Envoyé par ZZZzzz2 Voir le message
    phplive>

    Merci pour ton explication même si personnellement j'avais déjà à peu près compris ce qu'il se passait sauf peut-être au niveau des compteurs de référence justement, en effet je trouve que désactiver le compteur de référence en utilisant un 'const' n'est pas forcément logique.
    je suis partagé sur la question, disons que dans 99% des cas c'est inutile mais que tu mets en valeur un cas ou ça le serait

    Citation Envoyé par ZZZzzz2 Voir le message
    En fait comment justifies-tu au développeur que le bug est corrigé si on retire le mot clé "const", d'un point de vue algorithmique il n'y a aucune logique.
    si c'est logique, car en supprimant "const" tu envoie une copie de la chaine ...elle reste constante car tu ne la modifie pas, mais rien ne t'empêche de le faire

    Citation Envoyé par ZZZzzz2 Voir le message
    En fait pour moi c'est simple, les arguments ont pour durée le scope de la fonction, donc durant ce temps le compilateur doit garder une référence ou tout autre type de lien sur ces données. une information qui dit "garde ces données" (ici le contenu de "Message1") et à priori ce n'est pas le cas. C'est un peu décevant, l'aide devrait au moins expliquer ces effets de bord possibles.

    le problème c'est que c'est pas toi qui compile le code et Delphi ne voit pas les choses comme toi

    la doc pourrait difficilement noter tous les effets de bords ... comme celui ci directement inspiré de ton code

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     
    type
     TCallBack=procedure of object;
     
    var
     obj : TObject;
     
    procedure Tform1.FreeObject;
    begin
     obj.Free;
    end;
     
    procedure TForm1.Button2Click(Sender: TObject);
    begin
     obj:=TButton.Create(nil);
     Test2(obj,FreeObject);
    end;
     
    procedure TForm1.Test2(const obj:TObject; CallBack:TCallBack);
    begin
     ShowMessage(obj.ClassName);
     CallBack;
     ShowMessage(obj.ClassName);
    end;

Discussions similaires

  1. event dans une dll delphi a recuperer dans un autre langage
    Par titou640 dans le forum API, COM et SDKs
    Réponses: 12
    Dernier message: 30/09/2011, 12h05
  2. Tout le langage Delphi dans un document.
    Par ornitho dans le forum Langage
    Réponses: 4
    Dernier message: 10/12/2008, 14h52
  3. Bug dans Delphi 7 ?
    Par Teddy dans le forum Delphi
    Réponses: 8
    Dernier message: 26/06/2007, 19h29
  4. Bug dans delphi 2006? Include()
    Par the big ben 5 dans le forum Delphi .NET
    Réponses: 5
    Dernier message: 08/11/2006, 13h42
  5. Dans quel langage a été écrit le compilateur Delphi ?
    Par maamar1979 dans le forum Langage
    Réponses: 1
    Dernier message: 08/07/2006, 09h43

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo