Как протекать строка в Delphi

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

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

Ответ 1

Фактически, передача строки как CONST или non 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.

Ответ 2

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

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

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

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

Ответ 3

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

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 является константой, поэтому он ожидает, что фактический параметр сохранит свое исходное значение. Утверждение терпит неудачу.

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

Единственный способ утечки строки - рассматривать ее как нечто, отличное от строки, или использовать функции управления памятью, не поддерживающие тип. Ответ Mghie описывает, как обрабатывать строку как нечто, отличное от строки, с помощью 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);

Ответ 4

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

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.