Карта сайта Kansoftware
НОВОСТИУСЛУГИРЕШЕНИЯКОНТАКТЫ
KANSoftWare

Решение проблемы с памятью в FPC 3.2.2 при использовании вложенных и generic классов в Delphi и Pascal.

Delphi , Синтаксис , Память и Указатели

 

В данной статье мы рассмотрим проблему, возникшую при использовании вложенных и generic классов в FPC 3.2.2 (Free Pascal Compiler) и предложим решение, основанное на опыте, изложенном в обсуждении на форуме. Проблема проявляется как исключение, связанное с управлением памятью, при работе с перечислителем (enumerator) для generic списка.

Описание проблемы

Автор столкнулся с исключением при попытке итерировать по списку, созданному с использованием generic класса TList<T1> с вложенным классом Enumerator и ReverseEnumerator. Исключение возникало в строке кода, где происходило обращение к свойству Current перечислителя.

Исходный код, вызывавший проблему

{$mode objfpc}{$H+}
{$modeswitch generics}
unit Container;

interface

type
  generic TList<T1> = class
  private
    FValue  : T1;
    FTop    : specialize TList<T1>;
    FBottom : specialize TList<T1>;
    FPrev   : specialize TList<T1>;
    FNext   : specialize TList<T1>;
    FCount  : Integer;
  type
    Enumerator = class
    private
      FCurrent : specialize TList<T1>;
      FHead    : specialize TList<T1>;
    public
      constructor Create(AList: specialize TList<T1>);
      function GetCurrent: T1;
      function MoveNext: Boolean;
      property Current: T1 read GetCurrent;
    end;
    ReverseEnumerator = class
    private
      FCurrent: specialize TList<T1>;
    public
      constructor Create(AList: specialize TList<T1>);
      function MoveNext: Boolean;
      function GetCurrent: T1;
      property Current: T1 read GetCurrent;
    end;
  public
    constructor Create(AValue: T1);
    procedure Add(AValue: T1);
    function GetEnumerator: Enumerator;
    function GetReverseEnumerator: ReverseEnumerator;
  published
    property Count: Integer read FCount;
  end;

  generic TListVector<T1> = class(specialize TList<T1>)
  public
    constructor Create(AValue: T1);
  end;

  generic TMap<T1, T2> = class
  private
    FValue1 : T1;
    FValue2 : T2;
    FCount  : Integer;
  public
    constructor Create(AValue1: T1; AValue2: T2);
  end;

implementation

{ TListEnumerator }
constructor TList.Enumerator.Create(AList: specialize TList<T1>);
begin
  inherited Create;
  FCurrent := nil;
  FHead := AList.FTop;
end;

function TList.Enumerator.MoveNext: Boolean;
begin
  if FCurrent = nil then
    FCurrent := FHead
  else
    FCurrent := FCurrent.FNext;
  result   := FCurrent <> nil;
end;

function TList.Enumerator.GetCurrent: T1;
begin
  Result := FCurrent.FValue;
end;

{ TListReverseEnumerator }
constructor TList.ReverseEnumerator.Create(AList: specialize TList<T1>);
begin
  inherited Create;
  FCurrent := AList.FBottom; // Beginne ganz unten
end;

function TList.ReverseEnumerator.MoveNext: Boolean;
begin
  Result := FCurrent <> nil;
  if Result then
    FCurrent := FCurrent.FPrev;
  result   := FCurrent <> nil;  // Ошибка здесь!
end;

function TList.ReverseEnumerator.GetCurrent: T1;
begin
  Result := FCurrent.FValue;
end;

{ TListVector }
constructor TListVector.Create(AValue: T1);
begin
  inherited Create(AValue);
end;

{ TList }
constructor TList.Create(AValue: T1);
begin
  inherited Create;
  FValue  := AValue;
  FTop    := self;
  FBottom := self;
  FNext   := nil;
  FCount  := 1;
end;

procedure TList.Add(AValue: T1);
var
  tmp: specialize TList<T1>;
begin
  tmp := TList.Create(AValue);
  FBottom.FNext := tmp;
  FBottom       := tmp;
  inc(FCount);
end;

function TList.GetEnumerator: Enumerator;
begin
  result := Enumerator.Create(Self);
end;

function TList.GetReverseEnumerator: TList.ReverseEnumerator;
begin
  result := ReverseEnumerator.Create(Self);
end;

constructor TMap.Create(AValue1: T1; AValue2: T2);
begin
  inherited Create;
end;

end.
{$mode objfpc}{$H+}
{$M-}
{$define DLLIMPORT}
program test;

uses
  Container;

type
  TVector        = specialize TListVector< Integer >;
  TVectorReverse = TVector.ReverseEnumerator;

var
  V     : TVector;
  REnum : TVectorReverse;

begin
  V := TVector.Create(12);
  V.Add(2);
  V.Add(3);
  V.Add(4);

  REnum := V.GetReverseEnumerator;
  writeln('reeee');
  while REnum.MoveNext do
    writeln('Value: ', intToStr(REnum.Current));

  REnum.Free;
  V.Free;
end.

Решение

Проблема заключалась в логике работы метода MoveNext класса ReverseEnumerator. В оригинальном коде, если FCurrent указывал на первый элемент списка (последний при обратном перечислении), Result устанавливался в True, а затем FCurrent обнулялся. В результате, при следующем вызове GetCurrent происходило обращение к nil, что приводило к исключению.

Предложенное решение заключается в изменении логики MoveNext для ReverseEnumerator следующим образом:

constructor TList.ReverseEnumerator.Create(AList: specialize TList<T1>);
begin
  inherited Create;
  FCurrent := AList.FBottom; // Beginne ganz unten
  FStart   := false;
end;

function TList.ReverseEnumerator.MoveNext: Boolean;
begin
  if not FStart then
  begin
    FStart := true;
    result := FCurrent <> nil;
  end else
  begin
    if FCurrent <> nil then
      FCurrent := FCurrent.FPrev;
    result   := FCurrent <> nil;
  end;
end;

В этом исправленном коде добавлена переменная FStart: Boolean, которая контролирует, был ли уже выполнен первый шаг итерации. При первом вызове MoveNext, FStart равна false, поэтому устанавливается в true и возвращается true, если FCurrent не равен nil. В последующих вызовах MoveNext происходит переход к предыдущему элементу и проверка на nil.

Альтернативное решение

Более элегантным решением было бы приведение логики ReverseEnumerator к логике Enumerator, как и было предложено в обсуждении:

constructor TList.ReverseEnumerator.Create(AList: specialize TList<T1>);
begin
  inherited Create;
  FCurrent := nil;
  FHead := AList.FBottom;
end;

function TList.ReverseEnumerator.MoveNext: Boolean;
begin
  if FCurrent = nil then
    FCurrent := FHead
  else
    FCurrent := FCurrent.FPrev;
  result   := FCurrent <> nil;
end;

Это решение полностью соответствует логике прямого перечислителя, только начинает с конца списка.

Дополнительные замечания

  • Приватность вложенных типов: Важно помнить, что вложенные типы, объявленные в секции private, имеют область видимости уровня модуля (unit scope), а не класса. Это означает, что к ним можно получить доступ из любого места в том же модуле, даже вне класса, в котором они определены. Для ограничения видимости на уровне класса следует использовать strict private.
  • Версии компилятора: Автор отметил, что проблема возникала в FPC 3.3.1, но не в FPC 3.2.2. Это подчеркивает важность использования стабильных версий компилятора и проверки кода на разных версиях для выявления потенциальных ошибок.
  • Отладка: При возникновении подобных проблем рекомендуется использовать отладчик (например, GDB) для пошагового выполнения кода и анализа значений переменных, чтобы точно определить место возникновения ошибки.

Заключение

Проблема с памятью, возникшая при использовании вложенных и generic классов в FPC 3.2.2, была вызвана ошибкой в логике работы обратного перечислителя. Предложенные решения, включающие исправление логики MoveNext и приведение ее к логике прямого перечислителя, позволяют избежать исключения и корректно итерировать по списку в обратном порядке. Важно помнить о нюансах приватности вложенных типов и использовать стабильные версии компилятора для разработки надежного кода.

Создано по материалам из источника по ссылке.

В статье рассматривается и предлагается решение проблемы с управлением памятью в FPC 3.2.2, возникающей при использовании вложенных и generic классов в Delphi и Pascal, проявляющейся в исключении при итерации по generic списку в обратном порядке.


Комментарии и вопросы

Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS




Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.


:: Главная :: Память и Указатели ::


реклама


©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007
Top.Mail.Ru

Время компиляции файла: 2024-12-22 17:14:06
2025-11-04 17:54:06/0.010361909866333/0