2009-07-15 1 views
10

Я разговаривал с коллегой на днях о том, как вы можете просочиться в Delphi, если вы действительно испортите вещи. По умолчанию строки ссылаются на подсчеты и автоматически распределяются, поэтому они обычно работают без всякой мысли - нет необходимости в ручном распределении, вычислении размера или управлении памятью.Как просочиться в Delphi

Но я помню, как однажды прочитал, что есть способ непосредственно протекать по строке (без включения ее в объект, который просочился). Похоже, что это как-то связано с передачей строки по ссылке и последующим доступом к ней из большей области видимости в рамках той процедуры, в которую она была передана. Да, я знаю, что это расплывчато, поэтому я задаю здесь вопрос.

ответ

2

На самом деле, передавая строку как CONST или не сопзИте одинаковы в сроке счетчика ссылок в Delphi 2007 и 2009. Был случай, вызывающий нарушение прав, когда строка передается как CONST. Здесь проблема одна

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

Это похоже на то, что я думал, но это не AV в 2009 году, и я считаю, что вы говорили. –

+3

Это не AV, потому что в Delphi 2009 «const» теряет свою функциональность, если $ STRINGCHECKS включены. –

7

Я не знаю о проблеме в вашем втором абзаце, но однажды я был укушен просочившимися струнами в записи.

Если вы вызываете FillChar() на запись, содержащую строки, вы перезаписываете счетчик ссылок и адрес динамически распределенной памяти с нулями. Если строка не пуста, это приведет к утечке памяти. Путь вокруг этого - вызвать Завершить() на записи перед очисткой памяти, которую он занимает.

К сожалению, при вызове Finalize(), когда нет элементов записи, которые требуют финализации, вызывает подсказку компилятора. Случилось так, что я прокомментировал вызов Finalize(), чтобы заставить замолчать подсказку, но позже, когда я добавил член строки к записи, которую я пропустил, раскомментирую вызов, поэтому была введена утечка. К счастью, я обычно использую диспетчер памяти FastMM в самых подробных и параноидальных настройках в режиме отладки, поэтому утечка не осталась незамеченной.

Подсказка компилятора, вероятно, не такая уж хорошая вещь, молча опускающая вызов Finalize(), если он не нужен, будет намного лучше ИМХО.

+0

> бесшумный отказ от вызова Finalize(), если он не нужен, будет намного лучше IMHO +1 –

+0

Знаете ли вы, если это повлияет на короткие строки? –

+0

@JamesB: Нет, короткая строка не влияет на это, поскольку они не подсчитываются и не состоят из указателей на куски памяти, которые могут быть потеряны. Короткие строки состоят из одного байта длины и символьных данных, поэтому заполнение их '0' просто устанавливает длину '0' и устанавливает все элементы в' # 0'. – mghie

4

Нет, я не думаю, что такое может случиться. Строковая переменная может получить значение, которого вы не ожидали, но это не приведет к утечке памяти. Рассмотрим это:

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; 

Здесь One «s аргумент объявляться константой, поэтому, мол, он не изменится. Но тогда One обходит это путем изменения фактического параметра вместо формального параметра. Процедура Two «знает», что аргумент One является константой, поэтому он ожидает, что фактический параметр сохранит свое первоначальное значение. Утверждение терпит неудачу.

Строка не просочилась, но этот код демонстрирует, как вы можете получить ссылку обвисшей ссылки для строки. Arg - это локальный псевдоним Global. Хотя мы изменили значение Global, значение Arg остается нетронутым, и поскольку оно было объявлено как const, счетчик ссылок строки не увеличивался при входе в функцию. Переназначение Global сбросило счетчик ссылок до нуля, и строка была уничтожена. Объявление Arg как var имеет ту же проблему; передача его по значению устраняет эту проблему. (Вызов UniqueString предназначен только для того, чтобы гарантировать, что строка подсчитана по ссылке. В противном случае это может быть литерал строки без ссылки.) Все типы, управляемые с помощью компилятора, восприимчивы к этой проблеме; простые типы являются иммунными.

Единственный способ утечки строки - рассматривать ее как нечто, отличное от строки, или использовать функции управления памятью, не поддерживающие тип.Mghie's answer описывает, как обрабатывать строку как нечто, отличное от строки, с помощью FillChar для сглаживания строковой переменной. Функции памяти, отличные от типа, включают GetMem и FreeMem. Например:

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; 

Есть два способа исправить это. Одним из них является называть Initialize и Finalize:

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

Другим заключается в использовании типа-зависимых функций:

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

AFAICS Глобальный пример работает для любого типа переменной, поэтому, вероятно, это не тот, что после Джима. –

+1

Ах, ты прав. Но как строка, она * может * вызвать дополнительные проблемы, поскольку я собираюсь продемонстрировать, когда я отредактирую этот ответ. –

0

Я думаю this может быть похоже на то, что я имел в виду. Это обратная утечка строки, строка, которая получает собранную рано:

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

У вас есть указатель на объект в стеке, который вышел из сферы действия. Что вы ожидаете??? Независимо от того, очищена ли строка, это может привести к удару. –

+2

Лорен, вы * ожидаете * все, чтобы работать точно так, как вы планировали, независимо от того, какой код вы написали! Компилятор сегодня психический, не так ли? –

+0

Обязательно соглашайтесь с Лореном здесь. Второй вы вызываете символ @, все ставки отключены. Вы предлагаете компилятору вежливое прощание с любыми механизмами безопасности и сравнения ссылок и взятием необработанной памяти в свои руки. –