Management объект в не management памяти

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Management объект в не management памяти

Сообщение Дож » 11.07.2018 08:03:43

Рассмотрим код:
Код: Выделить всё
{$MODE OBJFPC}
type
TObj = object
  S: AnsiString;
  constructor Init(const Value: AnsiString);
  destructor Done; virtual;
end;

constructor TObj.Init(const Value: AnsiString);
begin
  S := Value;
end;

destructor TObj.Done;
begin
end;

var
  P: Pointer;

begin
  P := GetMem(SizeOf(TObj));
  TObj(P^).Init(ParamStr(0) + ' (modification)'); // <- созданная тут строка утечёт
  TObj(P^).Done;
  FreeMem(P);
end.


На первый взгляд с кодом всё в порядке: память из-под объекта мы в конце удаляем, а для создания и уничтожения используем честные конструктор и деструктор. Но в коде есть упомянутая утечка памяти:
Код: Выделить всё
D:\data\temp>fpc -gh -gl m.pas && m.exe
Free Pascal Compiler version 3.0.4 [2017/10/06] for i386
Copyright (c) 1993-2017 by Florian Klaempfl and others
Target OS: Win32 for i386
Compiling m.pas
Linking m.exe
26 lines compiled, 0.1 sec, 46704 bytes code, 1908 bytes data
Heap dump by heaptrc unit
3 memory blocks allocated : 85/88
2 memory blocks freed     : 39/40
1 unfreed memory blocks : 46
True heap size : 163840 (80 used in System startup)
True free heap : 163632
Should be : 163648
Call trace for block $034B15C0 size 46
  $004015AA  main,  line 23 of m.pas


Как я понимаю данную проблему: деструктор считается обычной процедурой (легаси от Turbo Pascal), поэтому он не приводит к освобождению строки и она утекает.

В данном конкретном случае проблема решается заменой GetMem и FreeMem на New и Dispose. Но представим себе несколько других ситуаций, когда New не поможет:
1. Я хочу выделить память сразу под 100 объектов одним вызовом GetMem
2. Я размещаю объекты в некотором пуле, какие именно - станет известно в рантайме

Как мне правильно уничтожать объекты в подобных ситуациях? Может есть какой-нибудь MODESWITCH, который делает так, что деструктор освобождает все управляемые поля в объекте?
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Management объект в не management памяти

Сообщение zub » 11.07.2018 08:10:55

Код: Выделить всё
destructor TObj.Done;
begin
  S := '';
end;

зачем усложнять?
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Management объект в не management памяти

Сообщение Дож » 11.07.2018 08:17:22

зачем усложнять?

1. За всеми строками и дин.массивами уследить сложно, особенно когда приучен к тому, что компилятор сам о них заботится
2. Какой тогда смысл в AnsiString, если даже деструктор у него руками вызывать? :)
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Management объект в не management памяти

Сообщение zub » 11.07.2018 12:29:43

Код: Выделить всё
{$MODE OBJFPC}
type
TObj = object
  S: AnsiString;
  constructor Init(const Value: AnsiString);
  destructor Done; virtual;
end;

constructor TObj.Init(const Value: AnsiString);
begin
  S := Value;
end;

destructor TObj.Done;
begin
end;

var
  P: Pointer;

begin
  P := GetMem(SizeOf(TObj));
  TObj(P^).Init(ParamStr(0) + ' (modification)'); // <- созданная тут строка утечёт
  finalize(TObj(P^));
  //TObj(P^).Done;
  FreeMem(P);
end.

но правильно всеравно S := ''; в деструкторе
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Management объект в не management памяти

Сообщение Дож » 11.07.2018 12:57:10

Вау! Спасибо!

Но деструктор-то почему закомментирован?
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Management объект в не management памяти

Сообщение zub » 11.07.2018 13:35:44

хз, случайно так скопипастил
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Management объект в не management памяти

Сообщение runewalsh » 13.07.2018 11:47:45

New разворачивается в GetMem + Initialize, Dispose — в Finalize + FreeMem, т. о. если сырой блок памяти уже на руках — нужно довызвать Initialize/Finalize. У тебя без Initialize «случайно» работает потому, что конструктор первым делом забивает объект нулями.

У них есть даже синтаксис для массивов!
Код: Выделить всё
type
   SomeStruct = record
      s: ansistring;
   end;

var
   P: ^SomeStruct;

begin
   P := GetMem(SizeOf(SomeStruct) * 10);
   Initialize(P^, 10);
   Finalize(P^, 10);
   FreeMem(P);
end.

(версии с произвольной RTTI — InitializeArray/FinalizeArray, а Initialize/Finalize выводят RTTI в compile-time из типа первого аргумента).

Не советую сейчас использовать object'ы. Где нужно наследование — используй class, где не нужно — record с {$MODESWITCH ADVANCEDRECORDS}. Например, в «advanced records», и только в них, в транке компилятора завезли (а значит, наверняка будут в следующем релизе) operator Initialize/Finalize/Copy/AddRef, позволяющие реализовать RAII без интерфейсов, автоподсчёт ссылок и т. д. для своих record-типов.
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 578
Зарегистрирован: 27.04.2010 00:15:25

Re: Management объект в не management памяти

Сообщение Дож » 13.07.2018 19:03:47

Подобные советы по отказу от object'ов я слышал много раз, хотелось бы знать аргументацию :)
Аватара пользователя
Дож
энтузиаст
 
Сообщения: 899
Зарегистрирован: 12.10.2008 16:14:47

Re: Management объект в не management памяти

Сообщение zub » 13.07.2018 19:41:32

Я тоже активно юзаю objectы, ставил эксперименты в своих задачах, разницы между обжэктами\ классами почти нет. По скорости - в пределах погрешности. по памяти десяток-сотня мегабайт теперь не разница((
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Management объект в не management памяти

Сообщение runewalsh » 14.07.2018 00:52:05

Говорю же, уже начинают потихонечку добавлять новые фичи, которые будут у advanced records, но (по-видимому) не будет у object.
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 578
Зарегистрирован: 27.04.2010 00:15:25

Re: Management объект в не management памяти

Сообщение serbod » 14.07.2018 11:47:13

Фу, какое уродство сделали. Ведь есть же стандартный механизм - конструктор и деструктор. Есть object type, который уже десятки лет работает. Если уж решили во избежание путаницы class/object делать managed record, то логично туда весь синтаксис и принцип работы из object перенести и сделать object синонимом record. Но нет, придумали костыль с двумя суставами. При этом счётчик ссылок для классов так и не думают делать. Ну что за херня!
Аватара пользователя
serbod
постоялец
 
Сообщения: 449
Зарегистрирован: 16.09.2016 11:03:02
Откуда: Минск

Re: Management объект в не management памяти

Сообщение runewalsh » 14.07.2018 15:14:24

Ну, наверное, потому и не сделали для объектов, что для них уже есть ручные деструкторы. (А может, сделали, просто я синтаксис не нашёл.) Никто не мешает засунуть такой в managed record, которая будет разрушать объект (или считать ссылочки) в своём operator Finalize. (Ололо, смартпоинтеры подвезли!)

Код: Выделить всё
{$if FPC_FULLVERSION < 30101} {$error FPC 3.1.1+ required} {$endif}
{$mode objfpc} {$modeswitch advancedrecords}
type
   PObj = ^TObj;
   TObj = object
      constructor Init;
      destructor Done; virtual;
      procedure Foo;
   end;

   constructor TObj.Init;
   begin
      writeln('TObj.Init');
   end;

   destructor TObj.Done;
   begin
      writeln('TObj.Done');
   end;

   procedure TObj.Foo;
   begin
      writeln('TObj.Foo');
   end;

type
   TObjRef = record
      obj: PObj;
      class operator Initialize(var r: TObjRef);
      class operator Finalize(var r: TObjRef);
   end;

   class operator TObjRef.Initialize(var r: TObjRef);
   begin
      r.obj := nil;
   end;

   class operator TObjRef.Finalize(var r: TObjRef);
   begin
      if Assigned(r.obj) then dispose(r.obj, Done);
      r.obj := nil;
   end;

var
   r: TObjRef;

begin
   r.obj := new(PObj, Init);
   r.obj^.Foo;
end.

>то логично туда весь синтаксис и принцип работы из object перенести и сделать object синонимом record
Я так думаю, что record выделили отдельно, т. к. она принципиально не допускает виртуальность. Это позволяет разрешить доступ к ней на месте (а не только по указателю, как с классами) без страха столкнуться с object slicing.
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 578
Зарегистрирован: 27.04.2010 00:15:25

Re: Management объект в не management памяти

Сообщение zub » 14.07.2018 21:27:11

>> У тебя без Initialize «случайно» работает потому, что конструктор первым делом забивает объект нулями.
конструктор емнип ничего не забивает, работает случайно
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

Re: Management объект в не management памяти

Сообщение runewalsh » 14.07.2018 22:52:37

Забивает, я проверил перед тем постом.
Код: Выделить всё
type
   TObj = object
      data: array[0 .. 9999] of byte;
      constructor Init;
      function Zeroed: boolean;
   end;

   constructor TObj.Init;
   begin
   end;

   function TObj.Zeroed: boolean;
   var
      i: SizeInt;
   begin
      for i := 0 to High(data) do
         if data[i] <> 0 then exit(false);
      result := true;
   end;

var
   o: TObj;

begin
   o.data[123] := 1;
   writeln('zeroed: ', o.Zeroed);
   o.Init;
   writeln('zeroed after Init: ', o.Zeroed);
end.

Кроме того, у меня самого были утечки, связанные с тем, что если у object'а с заполненными авто-полями вызвать конструктор, он их грубо затрёт. Даже после деструктора, т. к. деструктор не выполняет финализацию авто-полей.
Аватара пользователя
runewalsh
энтузиаст
 
Сообщения: 578
Зарегистрирован: 27.04.2010 00:15:25

Re: Management объект в не management памяти

Сообщение zub » 15.07.2018 10:29:11

Это документированое поведение? я сейчас не проверял.
Раньше в конструкторе приходилось ручками pointer(stringfield):=nil; делать перед присвоением начального значения, иначе там сидел мусор после getmem.
zub
долгожитель
 
Сообщения: 2884
Зарегистрирован: 14.11.2005 23:51:26

След.

Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 3

Рейтинг@Mail.ru