Как пропустить строку в Delphi - PullRequest
10 голосов
/ 15 июля 2009

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

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

Ответы [ 5 ]

7 голосов
/ 15 июля 2009

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

Если вы вызываете FillChar () для записи, содержащей строки, вы перезаписываете счетчик ссылок и адрес динамически выделенной памяти нулями. Если строка не пуста, это приведет к утечке памяти. Чтобы обойти это, нужно вызвать Finalize () для записи перед очисткой памяти, которую она занимает.

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

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

4 голосов
/ 15 июля 2009

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

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 объявлен как const, поэтому, предположительно, он не изменится. Но тогда One обходит это, изменяя фактический параметр вместо формального параметра. Процедура Two «знает», что аргумент One является const, поэтому она ожидает, что фактический параметр сохранит свое первоначальное значение. Утверждение не удалось.

Строка не просочилась, но этот код демонстрирует, как вы можете получить свисающую ссылку для строки. 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);
2 голосов
/ 16 июля 2009

На самом деле, передаваемая строка как CONST или не 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 голосов
/ 28 марта 2011

Другой способ утечки строки - объявить ее как переменную threadvar. См. мой вопрос для деталей. А решение см. Решение о том, как привести в порядок .

0 голосов
/ 16 июля 2009

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

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.
...