2009-07-15 8 views
10

Je parlais à un collègue l'autre jour sur la façon dont vous pouvez fuir une chaîne dans Delphi si vous vraiment gâcher les choses. Par défaut, les chaînes sont comptées et attribuées automatiquement, de sorte qu'elles fonctionnent généralement sans aucune réflexion - pas besoin d'allocation manuelle, de calculs de taille ou de gestion de la mémoire. Mais je me souviens avoir lu une fois qu'il y avait un moyen de fuir une chaîne directement (sans l'inclure dans un objet qui fuit). Il semble que cela ait quelque chose à voir avec le passage d'une chaîne par référence et l'accès à partir d'une plus grande portée à partir de la routine à laquelle elle a été transmise. Oui, je sais que c'est vague, c'est pourquoi je pose la question ici.Comment faire pour fuir une chaîne dans Delphi

Répondre

2

En fait, la chaîne passe comme CONST ou non const sont les mêmes en terme de nombre de référence Delphi 2007 et 2009. Il y avait un cas qui causait une violation d'accès lorsque la chaîne est passée comme CONST. Voici le problème un

type 
    TFoo = class 
    S: string; 
    procedure Foo(const S1: string); 
    end; 

procedure TFoo.Foo(const S1: string); 
begin 
    S:= S1; //access violation 
end; 

var 
    F: TFoo; 
begin 
    F:= TFoo.create; 
    try 
    F.S := 'S'; 
    F.Foo(F.S); 
    finally 
    F.Free; 
    end; 
end. 
+0

Cela ressemble un peu à ce que je pensais, mais ce n'est pas AV en 2009, ce que je crois est ce que vous disiez. –

+3

Il ne fait pas d'AV car dans Delphi 2009 le "const" perd sa fonctionnalité si $ STRINGCHECKS est activé. –

7

Je ne connais pas le problème dans votre deuxième paragraphe, mais j'ai été mordu une fois par des fuites dans un enregistrement.

Si vous appelez FillChar() sur un enregistrement contenant des chaînes, vous écrasez le compte ref et l'adresse de la mémoire allouée dynamiquement avec des zéros. À moins que la chaîne ne soit vide, cela entraînera une fuite de la mémoire. Le moyen de contourner cela est d'appeler Finaliser() sur l'enregistrement avant d'effacer la mémoire qu'il occupe.

Malheureusement, l'appel de Finalize() lorsqu'il n'y a aucun membre de l'enregistrement nécessitant une finalisation entraîne un indice du compilateur. Il m'est arrivé que j'ai commenté l'appel Finalize() pour faire taire l'indice, mais plus tard, quand j'ai ajouté un membre de la chaîne à l'enregistrement, je n'ai pas manqué de commenter l'appel, donc une fuite a été introduite. Heureusement, j'utilise généralement le gestionnaire de mémoire FastMM dans les paramètres les plus verbeux et paranoïaques en mode débogage, donc la fuite n'est pas passée inaperçue. L'indice du compilateur n'est probablement pas une bonne chose, en omettant silencieusement l'appel Finalize() si ce n'est pas nécessaire serait beaucoup mieux à mon humble avis.

+0

> omettre silencieusement l'appel Finalize() si ce n'est pas nécessaire serait beaucoup mieux IMHO +1 –

+0

savez-vous si les chaînes courtes affectées par cela? –

+0

@JamesB: Non, les chaînes courtes ne sont pas affectées par ceci, car elles ne sont pas comptées par référence et ne sont pas constituées de pointeurs vers des segments de mémoire qui pourraient être perdus. Les chaînes courtes sont constituées d'un octet de longueur unique et des données de caractères, donc les remplir avec '0's définit simplement la longueur à' 0' et définit tous les éléments à '# 0'. – mghie

4

Non, je ne pense pas qu'une telle chose puisse arriver. Il est possible qu'une variable de chaîne obtienne une valeur inattendue, mais qu'elle ne fuit pas la mémoire. Considérez ceci:

var 
    Global: string; 

procedure One(const Arg: string); 
begin 
    Global := ''; 

    // Oops. This is an invalid reference now. Arg points to 
    // what Global used to refer to, which isn't there anymore. 
    writeln(Arg); 
end; 

procedure Two; 
begin 
    Global := 'foo'; 
    UniqueString(Global); 
    One(Global); 
    Assert(Global = 'foo', 'Uh-oh. The argument isn''t really const?'); 
end; 

Voici l » argument One est déclaré const, donc soi-disant, il ne changera pas. Mais alors One contourne cela en changeant le paramètre réel au lieu du paramètre formel. La procédure Two "sait" que l'argument One est const, de sorte qu'il s'attend à ce que le paramètre réel conserve sa valeur d'origine. L'assertion échoue.

La chaîne n'a pas été divulguée, mais ce code montre comment vous pouvez obtenir une référence dangling pour une chaîne. Arg est un alias local de Global. Bien que nous ayons modifié Global, la valeur de Arg reste intacte et, comme elle a été déclarée const, le nombre de références de la chaîne n'a pas été incrémenté lors de l'entrée dans la fonction. La réaffectation Global a laissé tomber le nombre de références à zéro et la chaîne a été détruite. Déclarer Arg comme var aurait le même problème; le passer en valeur permettrait de résoudre ce problème. (L'appel à UniqueString est juste pour s'assurer que la chaîne est comptée par référence, sinon, il peut s'agir d'un littéral de chaîne non compté par référence.) Tous les types gérés par compilateur sont sensibles à ce problème; les types simples sont immunisés. La seule façon de fuir une chaîne est de la traiter autrement que comme une chaîne ou d'utiliser des fonctions de gestion de la mémoire non compatibles avec le type.Mghie's answer décrit comment traiter une chaîne comme autre chose qu'une chaîne en utilisant FillChar pour tabuler une variable chaîne. Les fonctions de mémoire non compatibles avec le type incluent GetMem et FreeMem. Par exemple:

type 
    PRec = ^TRec; 
    TRec = record 
    field: string; 
    end; 

var 
    Rec: PRec; 
begin 
    GetMem(Rec, SizeOf(Rec^)); 
    // Oops. Rec^ is uninitialized. This assignment isn't safe. 
    Rec^.field := IntToStr(4); 
    // Even if the assignment were OK, FreeMem would leak the string. 
    FreeMem(Rec); 
end; 

Il existe deux façons de le réparer. L'un est d'appeler Initialize et Finalize:

GetMem(Rec, SizeOf(Rec^)); 
Initialize(Rec^); 
Rec^.field := IntToStr(4); 
Finalize(Rec^); 
FreeMem(Rec); 

L'autre est d'utiliser des fonctions de type-Aware:

New(Rec); 
Rec^.field := IntToStr(4); 
Dispose(Rec); 
+1

AFAICS l'exemple global fonctionne pour n'importe quel type de variable, donc ce n'est probablement pas ce que Jim est après. –

+1

Ah, vous avez raison. Mais comme une chaîne, il peut * causer * des problèmes supplémentaires, comme je suis sur le point de le démontrer quand j'édite cette réponse. –

0

Je pense que this aurait pu être semblable à ce que je pensais. Il est l'inverse d'une fuite de chaîne, une chaîne qui obtient recueillies au début:

var 
    p : ^String; 

procedure InitString; 
var 
    s, x : String; 
begin 
    s := 'A cool string!'; 
    x := s + '. Append something to make a copy in' + 
      'memory and generate a new string.'; 

    p := @x; 
end; 

begin 
    { Call a function that will generate a string } 
    InitString(); 

    { Write the value of the string (pointed to by p) } 
    WriteLn(p^); // Runtime error 105! 


    { Wait for a key press } 
    ReadLn; 
end. 
+3

Vous avez un pointeur vers un objet sur la pile qui est sorti de la portée. Qu'attendez-vous??? Que la corde soit nettoyée ou non, elle risque de souffler. –

+2

Loren, vous vous attendez à ce que tout fonctionne exactement comme vous le vouliez, quel que soit le code que vous avez écrit! Le compilateur est psychique de nos jours, n'est-ce pas? –

+0

Je suis d'accord avec Loren ici. La seconde fois que vous invoquez le symbole @, tous les paris sont désactivés. Vous offrez au compilateur un adieu poli à tous les mécanismes de sécurité de type et de comptage de références et prenez la mémoire brute entre vos mains. –

Questions connexes