Форум по Delphi программированию

Delphi Sources



Вернуться   Форум по Delphi программированию > Все о Delphi > [ "Начинающим" ]
Ник
Пароль
Регистрация <<         Правила форума         >> FAQ Пользователи Календарь Поиск Сообщения за сегодня Все разделы прочитаны

Ответ
 
Опции темы Поиск в этой теме Опции просмотра
  #1  
Старый 21.07.2022, 08:44
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию Изменение длинны массива

Про массивы написано много, но одного момента я уяснить так и не смог:
если увеличивать размер массива объектов, под дополнительные объекты просто будет выделена память, сами новые объекты надо создать самостоятельно. Вроде все понятно.
А вот что происходит с объектом когда он оказывается за пределами массива при уменьшении размера? По логике - вроде бы система должна "забыть" про этот объект и считать выделенную под него память свободной. Как это вообще работает, нужно ли уничтожать объект?
А ну да, объект создается как то так:
Код:
function TTask.IncProductList:TProduct;
begin
  SetLength(FProductList, Length(FProductList)+1);
  FProductList[High(FProductList)]:= TProduct.Create;
  Result:= FProductList[High(FProductList)];
end;

Последний раз редактировалось Помидоркин, 21.07.2022 в 08:47.
Ответить с цитированием
  #2  
Старый 21.07.2022, 17:30
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 8,003
Версия Delphi: 7, XE3, 10.2
Репутация: 49089
По умолчанию

В данном случае, массива объектов, на самом деле в массиве выделяется только память под указатели на объекты. Память под сами объекты выделяется отдельно при создании самого объекта. Таким образом, если ты уменьшишь массив не удалив предварительно сам объект, то получишь мемори лик (Memory leak).

Условно говоря, одна ячейка массива (берем 32-битное приложение, для 64-битного тоже самое, просто размеры больше) занимает 4 байта. Пусть сам объект занимает 1кб (поля + все нужое служебное типа таблицы динамических методов и т.д.). Тогда свм массив из 100 ячеек будет занимать 400 байт (ну там есть еще несколько байт на служебную инфу, но это фикс. размер для массива). Плюс еще отдельно выделено 100 кб для самих объектов.
При уменьшении размера массива на 10 элементов у тебя изменится только память, занимаемая массивом - теперь станет 360 байт. А вот если ты не вызвал деструкторы для самих объектов, то под объекты так и останется выделенно 100 кб. И при этом ты потеряешь ссылки на те 10 объектов, т.е. после этого эти 10 кб вообзе зависнут.

Все вышесказанное относится к классическим объектам, т.е. классам, унаследованным от TObject. Для COM объектов ситуация немного другая - там есть механизм счетчика ссылок и как только для инстанса он сравнивается с 0, то происходит автоматическое удаление объекта. Вроде что-то писали про подобную реализацию и для классических объектов, но, по крайней мере в 10.2, такого пока нет.

Надеюсь, объяснил достаточно понятно...

ЗЫ. Для хранения коллекции объектов тогда лучше использовать TObjectList. Там можно сделать так (при вызове конструктора списка надо в параметрах указать, что он является "владельцем" помещенных в него объектов), что при удалении элемента будет автоматически вызван деструктор этого объекта. Да и удалять тогда можно из произваольного места списка, а не только с конца, как в случае массива.

Последний раз редактировалось lmikle, 21.07.2022 в 17:36.
Ответить с цитированием
Этот пользователь сказал Спасибо lmikle за это полезное сообщение:
Помидоркин (25.07.2022)
  #3  
Старый 22.08.2022, 08:32
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Попробую не плодить тем, спрошу здесь
Цитата:
Сообщение от lmikle
лучше использовать TObjectList.
Тщательно рассмотрел данный вариант, все-таки излишняя функциональность, та же история и с TCollection. Буду делать свое "с нуля".
А вопрос возник следующий: как именно следует уничтожать объекты?
Нашел вот это
Код:
unit System;
...

destructor TObject.Destroy;
begin
end;

procedure TObject.Free;
begin
// under ARC, this method isn't actually called since the compiler translates
// the call to be a mere nil assignment to the instance variable, which then calls _InstClear
  if Self <> nil then
    Destroy;
end;
ну т.е. деструктор вроде как ничего не делает, а фри только проверяет нужно-ли ничего не делать или можно ничего не делать.
В одном месте прочитал туманное объяснение что TObject - прародитель всех объектов и поэтому его деструктор вызывается как-то хитро и он все-таки что-то делает, но подробностей я так и не понял. И в комменте написано: "в ARC этот метод фактически не вызывается, поскольку компилятор преобразует вызов в простое присваивание переменной экземпляра nil, которое затем вызывает _Its Clear"
Ну т.е. это и все, можно со спокойным сердцем считать что память из-под объекта освободилась?
И еще:
Код:
  TPartForProduct = class(TObject)
  private
    FIndx: Word;
    FCheckID: string;
    FCount: Byte;
  public
    property Indx: Word read FIndx write FIndx;
    property CheckID: string read FCheckID write FCheckID;
    property Count: Byte read FCount write FCount;
  end;

  TPartList = array of TPartForProduct;

  TProduct = class(TObject)
  private
    FName: string;
    FAlias: string;
    FPartList: TPartList;
    function GetPartListCount: Byte;
    procedure SetPartListCount(const Value: Byte);
  public
    function IncPartSet:TPartForProduct;
    property Name: string read FName write FName;
    property Alias: string read FAlias write FAlias;
    property PartList: TPartList read FPartList write FPartList;
    property PartListCount: Byte read GetPartListCount write SetPartListCount;
  end;
1- В TProduct.Destroy ведь нужно пройтись по массиву и так же вызвать TPartForProduct.Free?
2-На данный момент в объектах только строки и числа, а как быть со всякими StringList или JPEGImage? Их тоже удалять в ручную?
Ответить с цитированием
  #4  
Старый 23.08.2022, 01:07
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 8,003
Версия Delphi: 7, XE3, 10.2
Репутация: 49089
По умолчанию

Вообще, методы, объявленные в коде как constructor и destructor, таковыми на самом деле не являются. Правдя тут надо отметить, что доступа к реальным конструктору и деструктору в Дельфи просто нет (не считаем различные хаки). Поэтому данные методы и используются для реализации выделения памяти под атрибуты классов (имеется в виду сложные атрибуты, являющиеся так же объектами классов, память под простые типы выделяется именно реальным конструктором, но вызов его скрыт от программиста). Т.е. по сути constructor и destructor являются инициализатором и деинициализатором по своей сути. Это для начала.

Теперь по повду "излишней функциональности". Во первых, ее всегда можно скрыть:
Код:
type
  TMyObjectWithItems = class
  private
    FItems : TObjectList;
    function GetItem(Index : Integer) : TAnotherObject;
    function GetCount : Integer;
  ...
  public
    property Items[Index : Integer] : TAnotherObject read GetItem;
    property Count : Integer read GetCount;
  end;
...

function TMyObjectWithItems.GetItem(Index : Integer) : TAnotherObject;
begin
  Result := FItems[Index] As TAnotherObject;
end;

function TMyObjectWithItems.GetCount : Integer;
begin
  Result := FItems.Count;
end;

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

Теперь ответы на последние 2 вопроса.
1. Да, надо. Для проверки, если используешь одну из последних версий Дельфи, можешь включить отчет об утечках памяти. При закрытии приложения тебе менеджер памяти скажет, если есть утечки.
2. Да, их тоже надо удалять в деструкторе. В принципе, при правльном дизайне, это по одной строчке в конструкторе и деструкторе (создать и, соответсвенно, удалить).
Ответить с цитированием
Этот пользователь сказал Спасибо lmikle за это полезное сообщение:
Помидоркин (24.08.2022)
  #5  
Старый 23.08.2022, 01:09
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 8,003
Версия Delphi: 7, XE3, 10.2
Репутация: 49089
По умолчанию

Для примера, вот моделька из одного из моих проектов. Она простая, там всего 2 уровня - сама модель и ее итемы.
Код:
unit Model;

interface

uses
  Classes, SysUtils, Contnrs, DateUtils, Generics.Collections, Forms, ActiveX, ModelBase,
  System.Variants, Dialogs;

const
  sigFileMark : String = 'bhpvMonitor';
  sigFileVersion : Integer = 1;

type
  TMonitorItem = class(TModelItemBase)
  private
    FGroupName : String;

    FGUID : TGUID;
    FUrl : String;
    FItemName : String;
	  FComments : String;

    FInitPrice : Currency;
    FInitDate : TDateTime;

    FWantedPrice : Currency;

    FLastPrice : Currency;
    FLastDate : TDateTime;

    FOnHold : Boolean;
    FFlagged : Boolean;
  protected
    procedure CreateInitFields;

  public
    constructor Create(AUrl : String);
    constructor Load(AStream : TStream; ADataVer : Integer);

	  procedure Assign(AItem : TMonitorItem);

    procedure SaveToStream(AStream : TStream); override;
    procedure LoadFromStream(AStream : TStream); override;
    procedure LoadFromStreamVer(AStream : TStream; ADataVer : Integer); virtual;

    procedure UpdateData;

    property GUID : TGUID read FGUID;
    property Url : String read FUrl;

    property ItemName : String read FItemName write FItemName;

    property InitPrice : Currency read FInitPrice;
    property InitDate : TDateTime read FInitDate;

    property WantedPrice : Currency read FWantedPrice write FWantedPrice;

    property LastPrice : Currency read FLastPrice;
    property LastDate : TDateTime read FLastDate;
  end;

  TMonitorModel = class(TModelBase)
  private
    FItems : TObjectList<TMonitorItem>;

    function GetCount : Integer;
    function GetItem(Index : Integer) : TMonitorItem;
  protected
    procedure SaveToStream(AStream : TStream); override;
    procedure LoadFromStream(AStream : TStream); override;
  public
    constructor Create;
    destructor Destroy; override;

    procedure GetGroupsList(var AList : TStringList); overload;
    function GetGroupsList : TStringList; overload;

    procedure Clear;
    procedure AddItem(AItem : TMonitorItem);
    procedure DeleteItem(Index : Integer);
    procedure RemoveItem(AItem : TMonitorItem);
    function IndexOf(AItem : TMonitorItem) : Integer;
    function IndexOfByUrl(AUrl : String) : Integer;

    class function GetModelFileName : String;

    property Items[Index : Integer] : TMonitorItem read GetItem;
    property Count : Integer read GetCount;
  end;

implementation

uses
  IdHTTP, IdSSLOpenSSL, MSHTML, SysFolders;

{ TMonitorItem }

procedure TMonitorItem.CreateInitFields;
begin
  FGroupName := 'No group';
  
  CoCreateGUID(FGUID);
  FUrl := '';
  FItemName := '';
  FComments := '';

  FInitPrice := 0.0;
  FInitDate := 0;

  FWantedPrice := 0.0;

  FLastPrice := 0.0;
  FLastDate := 0;

  FOnHold := False;
  FFlagged := False;
end;

procedure TMonitorItem.Assign(AItem : TMonitorItem);
begin
  If Not Assigned(AItem)
    Then Raise Exception.Create('TMonitorItem.Assign: AItem parameter is not assigned.');

  Self.FGroupName := AItem.FGroupName;
  
  Self.FGUID := AItem.FGUID;
  Self.FUrl := AItem.FUrl;
  Self.FItemName := AItem.FItemName;
  Self.FComments := AItem.FComments;
  
  Self.FInitPrice := AItem.FInitPrice;
  Self.FInitDate := AItem.FInitDate;

  Self.FWantedPrice := AItem.FWantedPrice;

  Self.FLastPrice := AItem.FLastPrice;
  Self.FLastDate := AItem.FLastDate;

  Self.FOnHold := AItem.FOnHold;
  Self.FFlagged := AItem.FFlagged;
end;

procedure TMonitorItem.UpdateData;
var
  APage : String;
begin
//  APage := ReadInternetPage(FUrl);
//  SetValuesFromInternetPage(APage,False);
end;

constructor TMonitorItem.Create(AUrl : String);
var
  APage : String;
begin
  inherited Create;
  CreateInitFields;

  FUrl := AUrl;

  APage := ReadInternetPage(FUrl);
  SetValuesFromInternetPage(APage,True);
end;

constructor TMonitorItem.Load(AStream : TStream; ADataVer : Integer);
begin
  inherited Create;
  LoadFromStreamVer(AStream,ADataVer);
end;

procedure TMonitorItem.SaveToStream(AStream : TStream);
begin
  WriteString(AStream,FGroupName);
  
  WriteGUID(AStream,FGUID);
  WriteString(AStream,FUrl);
  WriteString(AStream,FItemName);
  WriteString(AStream,FComments);
  
  WriteCurrency(AStream,FInitPrice);
  WriteDateTime(AStream,FInitDate);

  WriteCurrency(AStream,FWantedPrice);

  WriteCurrency(AStream,FLastPrice);
  WriteDateTime(AStream,FLastDate);

  WriteBool(AStream,FOnHold);
  WriteBool(AStream,FFlagged);
end;

procedure TMonitorItem.LoadFromStream(AStream : TStream);
begin
  LoadFromStreamVer(AStream,-1);
end;

procedure TMonitorItem.LoadFromStreamVer(AStream : TStream; ADataVer : Integer);
begin
  If ADataVer = -1 Then ADataVer := sigFileVersion;

  FGroupName := ReadString(AStream);
  
  FGUID := ReadGUID(AStream);
  FUrl := ReadString(AStream);
  FItemName := ReadString(AStream);
  FComments := ReadString(AStream);

  FInitPrice := ReadCurrency(AStream);
  FInitDate := ReadDateTime(AStream);

  FWantedPrice := ReadCurrency(AStream);

  FLastPrice := ReadCurrency(AStream);
  FLastDate := ReadDateTime(AStream);

  FOnHold := ReadBool(AStream);
  FFlagged := ReadBool(AStream);

  If ADataVer > 1 Then // Version 2 and hier
    Begin

    End;
end;

{ TMonitorModel }

function TMonitorModel.GetCount : Integer;
begin
  Result := FItems.Count;
end;

function TMonitorModel.GetItem(Index : Integer) : TMonitorItem;
begin
  If (Index < 0) Or (Index > FItems.Count) Then
    Raise Exception.CreateFmt('GetItem. Index out of bounds (%d).',[Index]);
  Result := FItems[Index];
end;

constructor TMonitorModel.Create;
begin
  inherited;
  FItems := TObjectList<TMonitorItem>.Create(True);
end;

destructor TMonitorModel.Destroy;
begin
  FreeAndNil(FItems);
  inherited;
end;

procedure TMonitorModel.SaveToStream(AStream : TStream);
var
  I, ACnt : Integer;
begin
  AStream.WriteBuffer(sigFileMark[1],Length(sigFileMark)*SizeOf(Char));
  AStream.WriteBuffer(sigFileVersion,SizeOf(Integer));

  ACnt := FItems.Count;
  AStream.WriteBuffer(ACnt,SizeOf(Integer));
  For I := 0 To ACnt-1 Do
    Items[i].SaveToStream(AStream);
end;

procedure TMonitorModel.LoadFromStream(AStream : TStream);
var
  I, ACnt : Integer;
  AItem : TMonitorItem;
  rdMark : String;
  rdVersion : Integer;
begin
  FItems.Clear;

  SetLength(rdMark,Length(sigFileMark));
  AStream.ReadBuffer(rdMark[1],Length(sigFileMark)*SizeOf(Char));
  AStream.ReadBuffer(rdVersion,SizeOf(Integer));

  If rdMark <> sigFileMark Then
    Raise Exception.CreateFmt('Incorrect file signature (expected "%s", but got "%s").',[sigFileMark,rdMark]);
  If rdVersion > sigFileVersion Then
    Raise Exception.Createfmt('Wrong file version (expected less or equal %d, but found %d).',[sigFileVersion,rdVersion]);

  AStream.ReadBuffer(ACnt,SizeOf(Integer));
  For I := 0 To ACnt-1 Do
    Begin
      AItem := TMonitorItem.Load(AStream,rdVersion);
      FItems.Add(AItem);
    End;
end;

procedure TMonitorModel.GetGroupsList(var AList : TStringList);
var
  I, J : Integer;
  IsFound : Boolean;
begin
  If Not Assigned(AList)
    Then Raise Exception.Create('TMonitorModel.GetGroupsList: AList parameter is not assigned.');
	
  AList.Clear;
  For I := 0 To FItems.Count-1 Do
    Begin
	  IsFound := False;
	  For J := 0 To AList.Count-1 Do
	    Begin
		  IsFound := AnsiCompareText((FItems[i] As TMonitorItem).FGroupName,AList[J]) = 0;
		  If IsFound Then Break;
		End;
	  If Not IsFound 
	    Then AList.Add((FItems[i] As TMonitorItem).FGroupName);
    End;
end;

function TMonitorModel.GetGroupsList : TStringList;
begin
  Result := TStringList.Create;
  GetGroupsList(Result);
end;

procedure TMonitorModel.Clear;
begin
  FItems.Clear;
end;

procedure TMonitorModel.AddItem(AItem : TMonitorItem);
begin
  FItems.Add(AItem);
end;

procedure TMonitorModel.DeleteItem(Index : Integer);
begin
  FItems.Delete(Index);
end;

procedure TMonitorModel.RemoveItem(AItem : TMonitorItem);
begin
  FItems.Remove(AItem);
end;

function TMonitorModel.IndexOf(AItem : TMonitorItem) : Integer;
begin
  Result := FItems.IndexOf(AItem);
end;

function TMonitorModel.IndexOfByUrl(AUrl : String) : Integer;
var
  I : Integer;
begin
  Result := -1;
  For I := 0 To FItems.Count-1 Do
    If (FItems[i] As TMonitorItem).Url = AUrl Then
      Begin
        Result := I;
        Break;
      End;
end;

class function TMonitorModel.GetModelFileName : String;
var
  APath : String;
begin
  Try
    APath := GetUserAppDataFolderPath;
    APath := IncludeTrailingPathDelimiter(APath + 'bhpvMonitor');
    If Not DirectoryExists(APath) Then
      If Not ForceDirectories(APath) Then
        Raise Exception.Create('Can''t force user profile path.');
  Except
    APath := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName));
  End;
  Result := APath + 'items.dat';
end;

end.

PS. Пришлось удалить пару методов, что бы влезть в ограничение длинны сообщения...
Ответить с цитированием
Этот пользователь сказал Спасибо lmikle за это полезное сообщение:
Помидоркин (24.08.2022)
  #6  
Старый 22.09.2022, 11:38
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Еще один вопрос возник, что происходит с данными при уменьшении длинны массива? Например запись
Код:
  TPartOfAll = record
    Part: TPart;
    Total: Word;
    Done: Word;
  end;

  TAllParts = array of TPartOfAll;

.........
SetLength(FAllParts, 0);
С "Part: TPart" вроде понятно, сам объект уничтожать не надо, будет уничтожен только указатель на него, а вот что произойдет с числами?

Последний раз редактировалось Помидоркин, 22.09.2022 в 11:48.
Ответить с цитированием
  #7  
Старый 22.09.2022, 23:07
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 8,003
Версия Delphi: 7, XE3, 10.2
Репутация: 49089
По умолчанию

как раз объект и надо уничтожать - иначе он останется "висеть" в куче и удалить его не будет возможности, т.к. ссылка на него будет потеряна.

А вот с "числами" как раз все все нормально будет, бо как это не указатели. Они будут удалены автоматически. Память будет помечена как свободная и может быть переиспользованна. При следующем выделении памяти компилятор просто зачистит выделенное место, т.е. старых данных ты уже не увидишь.
Ответить с цитированием
Этот пользователь сказал Спасибо lmikle за это полезное сообщение:
Помидоркин (23.09.2022)
  #8  
Старый 23.09.2022, 17:17
Аватар для Помидоркин
Помидоркин Помидоркин вне форума
Начинающий
 
Регистрация: 07.10.2012
Адрес: Дедовск
Сообщения: 110
Версия Delphi: Rio 10.3
Репутация: 10
По умолчанию

Цитата:
Сообщение от lmikle
как раз объект и надо уничтожать...
я не совсем верно выразился, Part - не единственная ссылка на объект, и сам объект мне еще нужен.
Ну т.е. я так себе представляю - при задании длинны массива выделяется память (в моем случае) - (<сколько-то там под указатель>+2байта+2байта)*длинна массива, как-то так.
Ответить с цитированием
  #9  
Старый 23.09.2022, 19:48
lmikle lmikle вне форума
Модератор
 
Регистрация: 17.04.2008
Сообщения: 8,003
Версия Delphi: 7, XE3, 10.2
Репутация: 49089
По умолчанию

Ну, правильно представляеш в общем.
"<сколько-то там под указатель>" - это конкретно 32 или 64 бита (4 или 8 байт). Память под сам объект выделвется при его создании отдельно и к массиву отношения не имеет (это просто для уточнения).
Ну и еще там есть место под длинну массива (если мы говорим о динамических массивах), но оно тебе на прямую не доступно (находится по отрицательному адресу относительно адреса первого элемента массива). Опять же, это просто для общего понимания.

ЗЫ. Тут есть момент, связанный с оптимизацией. Для тебя он "прозрачен". А именно, как выделяется память при изменении размера массива. Если есть последовательно свободная память, то при увеличении размера массива она "прирезается" к уже выделенной. А вот ести нужного куска нет, то внутри происходит выделение полноразмерного куска в новом месте и копирование данных из старой в новую память. Поэтому для оптимизации рекомендуют либо заранее вычислить нужный размер массива и выделить память, либо выделять как минимум достаточно большими кусками. Кстати, именно так реализованы объекты-списки в VCL - они выделяют место кусками под несколько элементов и уже внутри себя контролируют использование.

Последний раз редактировалось lmikle, 23.09.2022 в 19:50.
Ответить с цитированием
Ответ


Delphi Sources

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB-коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход


Часовой пояс GMT +3, время: 14:43.


 

Сайт

Форум

FAQ

RSS лента

Прочее

 

Copyright © Форум "Delphi Sources" by BrokenByte Software, 2004-2023

ВКонтакте   Facebook   Twitter