Recent

Author Topic: memory management  (Read 1892 times)

srvaldez

  • Full Member
  • ***
  • Posts: 116
memory management
« on: October 13, 2024, 05:30:01 pm »
in reference to https://forum.lazarus.freepascal.org/index.php/topic,68290.msg533462.html#msg533462
warning: beginner code modification
Code: [Select]
var ReturnNilIfGrowHeapFails: Boolean;
type
 
  { TRec }
 
  TRec = record
  private
    FValue: Integer;
  public
    class operator Initialize(var aRec: TRec);
    procedure Check;
  end;
how would you handle if FValue was a pointer ?
something like the following
Code: [Select]
    type
     
        { TRec }
     
        TRec = record
        private
        FValue: pointer;
        Size: PtrUInt; //size of allocated memory
        public
        class operator Initialize(var aRec: TRec);
        class operator Finalize(var aRec: TRec);
        //procedure Check;
        end;

and perhaps
Code: [Select]
    class operator TRec.Initialize(var aRec: TRec);
    begin
        ReturnNilIfGrowHeapFails:=True;
        writeln('TRec.Initialize: before: FValue = ',ReturnNilIfGrowHeapFails); //, aRec.FValue);
        aRec.Size:=777*4;
        GetMem(aRec.FValue, aRec.Size);
        writeln('TRec.Initialize: after: FValue = ',ReturnNilIfGrowHeapFails); //, aRec.FValue);
    end;

    class operator TRec.Finalize(var aRec: TRec);
    begin
        WriteLn('Just to let you know: I am finalizing..');
        FreeMem(aRec.FValue, aRec.Size);
    end;
how do you check if memory allocation succeeded or failed ?
from the documentation I thought that the variable ReturnNilIfGrowHeapFails should be nil if it failed but I get a runtime error  instead -- if requested memory is too large
how would you catch a failure and exit gracefully ?
how do you check the value of aRec.FValue ?
« Last Edit: October 13, 2024, 05:37:22 pm by srvaldez »

Fibonacci

  • Hero Member
  • *****
  • Posts: 602
  • Internal Error Hunter
Re: memory management
« Reply #1 on: October 13, 2024, 05:57:17 pm »
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$modeswitch advancedrecords}
  4.  
  5. uses SysUtils;
  6.  
  7. type
  8.   TRec = record
  9.   private
  10.     FValue: Pointer;
  11.     Size: PtrUInt; //size of allocated memory
  12.   public
  13.     class operator Initialize(var aRec: TRec);
  14.   end;
  15.  
  16. class operator TRec.Initialize(var aRec: TRec);
  17. begin
  18.   ReturnNilIfGrowHeapFails := True;
  19.  
  20.   // too much
  21.   aRec.Size := 9999999999999;
  22.  
  23.   // enough
  24.   //aRec.Size := 9;
  25.  
  26.   try
  27.     GetMem(aRec.FValue, aRec.Size);
  28.   except
  29.     on E: Exception do writeln('fatal error: ', e.Message);
  30.   end;
  31. end;
  32.  
  33. var
  34.   t: TRec;
  35.  
  36. begin
  37.   writeln('t.Size = ', t.Size);
  38.   writeln('t.Pointer = ', PtrInt(t.FValue));
  39.   writeln('valid pointer? ', t.FValue <> nil);
  40.  
  41.   readln;
  42. end.

If ReturnNilIfGrowHeapFails is set to False you can catch an exception.
« Last Edit: October 13, 2024, 06:02:50 pm by Fibonacci »

srvaldez

  • Full Member
  • ***
  • Posts: 116
Re: memory management
« Reply #2 on: October 13, 2024, 06:03:11 pm »
thanks Fibonacci  :)
I had to add: {$mode objfpc} but otherwise it compiles and runs OK

srvaldez

  • Full Member
  • ***
  • Posts: 116
Re: memory management
« Reply #3 on: October 13, 2024, 06:28:16 pm »
one more question, how do you automatically initialize the variables of type TRec ?

Fibonacci

  • Hero Member
  • *****
  • Posts: 602
  • Internal Error Hunter
Re: memory management
« Reply #4 on: October 13, 2024, 06:41:21 pm »
Debugger shows its initialized just by existing in the var.

But perhaps you could add extra Default() here for safety, Im not sure how these auto-initialized record works. You need more reading 8)

Code: Pascal  [Select][+][-]
  1. class operator TRec.Initialize(var aRec: TRec);
  2. begin
  3.   aRec := Default(TRec);
  4.   // ...
  5. end;

srvaldez

  • Full Member
  • ***
  • Posts: 116
Re: memory management
« Reply #5 on: October 13, 2024, 06:48:12 pm »
Fibonnacci
I had already tried that but the compiler still gives the uninitialized warning
by the way, explicitly calling  Initialize(t); causes it to be initialized twice, I guess a compiler switch to silence the uninitialized warning would be needed

Fibonacci

  • Hero Member
  • *****
  • Posts: 602
  • Internal Error Hunter
Re: memory management
« Reply #6 on: October 13, 2024, 06:56:17 pm »
You can hide the warning, as I have shown in the screenshot

srvaldez

  • Full Member
  • ***
  • Posts: 116
Re: memory management
« Reply #7 on: October 13, 2024, 08:19:34 pm »
thank you  :)
here's my modified test
Code: [Select]
    program Project1;
    {$mode objfpc}
    {$modeswitch advancedrecords}

    {$warn 5090 off} //suppress uninitialized warning
    {$warn 5025 off} //suppress variable not used warning
    type
        TRec = record
        private
        FValue: Pointer;
        Size: PtrUInt; //size of allocated memory
        public
        class operator Initialize(var aRec: TRec);
        class operator Finalize(var aRec: TRec);
        end;
     
    class operator TRec.Initialize(var aRec: TRec);
    begin
        if (aRec.FValue = nil) then // initialize only if aRec.FValue = nil
        begin
            ReturnNilIfGrowHeapFails := True;
            writeln('TRec.Initialize: before: FValue = ',ReturnNilIfGrowHeapFails, 'FValue = ', PtrInt(aRec.FValue));
            // too much
            aRec.Size := 999; //9999999999;
         
            // enough
            //aRec.Size := 9;
         
            GetMem(aRec.FValue, aRec.Size);
            writeln('TRec.Initialize: after: FValue = ',ReturnNilIfGrowHeapFails, 'FValue = ', PtrInt(aRec.FValue));
        end;
    end;

    class operator TRec.Finalize(var aRec: TRec);
    begin
        WriteLn('Just to let you know: I am finalizing..');
        if PtrUInt(aRec.FValue)<>0 then
        begin
            FreeMem(aRec.FValue, aRec.Size);
            aRec.FValue := nil;
            writeln('aRec.Pointer after freeing = ', PtrInt(aRec.FValue))
        end
        else
            writeln('pointer is zero');
    end;
   
    var
        t, z, x: TRec;
     
    begin
        initialize(t); //  initialize(t) is not needed but it won't hurt, variables get initialized as soon as a member is accessed
        writeln('t.Size = ', t.Size);
        writeln('t.Pointer = ', PtrInt(t.FValue));
        writeln('valid pointer? ', t.FValue <> nil);
        writeln('t.Size = ', z.Size);
        writeln('t.Size = ', x.Size);
        readln;
    end.
« Last Edit: October 13, 2024, 08:59:32 pm by srvaldez »

srvaldez

  • Full Member
  • ***
  • Posts: 116
Re: memory management
« Reply #8 on: October 13, 2024, 10:12:05 pm »
a little more testing, in the procedure test variable b:TRec  is created, everything works except that there are no messages from the initializer, why is that ?
Code: [Select]
    program Project1;
    {$mode objfpc}
    {$modeswitch advancedrecords}

    {$warn 5090 off} //suppress uninitialized warning
    {$warn 5025 off} //suppress variable not used warning
    type
        TRec = record
        private
        FValue: ^longint;
        Size: PtrUInt; //size of allocated memory
        public
        class operator Initialize(var aRec: TRec);
        class operator Finalize(var aRec: TRec);
        end;
     
    class operator TRec.Initialize(var aRec: TRec);
    begin
        if (aRec.FValue = nil) then // initialize only if aRec.FValue = nil
        begin
            ReturnNilIfGrowHeapFails := True;
            writeln('TRec.Initialize: before: FValue = ',ReturnNilIfGrowHeapFails, 'FValue = ', PtrInt(aRec.FValue));
            aRec.Size := 10*4;
            GetMem(aRec.FValue, aRec.Size);
            writeln('TRec.Initialize: after: FValue = ',ReturnNilIfGrowHeapFails, 'FValue = ', PtrInt(aRec.FValue));
        end;
    end;

    class operator TRec.Finalize(var aRec: TRec);
    begin
        WriteLn('Just to let you know: I am finalizing..');
        if PtrUInt(aRec.FValue)<>0 then
        begin
            FreeMem(aRec.FValue, aRec.Size);
            aRec.FValue := nil;
            writeln('aRec.Pointer after freeing = ', PtrInt(aRec.FValue))
        end
        else
            writeln('pointer is zero');
    end;

    procedure test(var a:TRec);
    var b:TRec;

    begin
        initialize(b);
        writeln('b.Pointer = ', PtrInt(b.FValue));
        b.FValue[0]:=123;
        b.FValue[1]:=456;
        b.FValue[2]:=789;
        a.FValue[0]:=b.FValue[0];
        a.FValue[1]:=b.FValue[1];
        a.FValue[2]:=b.FValue[2];
    end;
   

    var
        t, z, x: TRec;
     
    begin
        test(x);
        initialize(t); //  initialize(t) is not needed but it won't hurt, variables get initialized as soon as a member is accessed
        writeln('t.Size = ', t.Size);
        writeln('t.Pointer = ', PtrInt(t.FValue));
        writeln('valid pointer? ', t.FValue <> nil);
        writeln('t.Size = ', z.Size);
        writeln('t.Size = ', x.Size);
        writeln('x.FValue[0] = ', x.FValue[0]);
        writeln('x.FValue[1] = ', x.FValue[1]);
        writeln('x.FValue[2] = ', x.FValue[2]);
        readln;
    end.

Quote
TRec.Initialize: before: FValue = TRUEFValue = 0
TRec.Initialize: after: FValue = TRUEFValue = 22349536
TRec.Initialize: before: FValue = TRUEFValue = 0
TRec.Initialize: after: FValue = TRUEFValue = 22349600
TRec.Initialize: before: FValue = TRUEFValue = 0
TRec.Initialize: after: FValue = TRUEFValue = 22349664
b.Pointer = 20971248
Just to let you know: I am finalizing..
aRec.Pointer after freeing = 0
t.Size = 40
t.Pointer = 22349536
valid pointer? TRUE
t.Size = 40
t.Size = 40
x.FValue[0] = 123
x.FValue[1] = 456
x.FValue[2] = 789

srvaldez

  • Full Member
  • ***
  • Posts: 116
Re: memory management
« Reply #9 on: October 13, 2024, 10:40:14 pm »
this whole thing does not work :(
variables created inside a procedure don't get initialized, this little test will crash
Code: Pascal  [Select][+][-]
  1.     program Project1;
  2.     {$mode objfpc}
  3.     {$modeswitch advancedrecords}
  4.  
  5.     {$warn 5090 off} //suppress uninitialized warning
  6.     {$warn 5025 off} //suppress variable not used warning
  7.     uses JwaWinBase;
  8.    
  9.     type
  10.         TRec = record
  11.         private
  12.         FValue: ^longint;
  13.         Size: PtrUInt; //size of allocated memory
  14.         public
  15.         class operator Initialize(var aRec: TRec);
  16.         class operator Finalize(var aRec: TRec);
  17.         end;
  18.      
  19.     class operator TRec.Initialize(var aRec: TRec);
  20.     begin
  21.         if (aRec.FValue = nil) then // initialize only if aRec.FValue = nil
  22.         begin
  23.             ReturnNilIfGrowHeapFails := True;
  24.             writeln('TRec.Initialize: before: FValue = ',ReturnNilIfGrowHeapFails, 'FValue = ', PtrInt(aRec.FValue));
  25.             aRec.Size := 1000*4;
  26.             GetMem(aRec.FValue, aRec.Size);
  27.             writeln('TRec.Initialize: after: FValue = ',ReturnNilIfGrowHeapFails, 'FValue = ', PtrInt(aRec.FValue));
  28.         end;
  29.     end;
  30.  
  31.     class operator TRec.Finalize(var aRec: TRec);
  32.     begin
  33.         WriteLn('Just to let you know: I am finalizing..');
  34.         if PtrUInt(aRec.FValue)<>0 then
  35.         begin
  36.             FreeMem(aRec.FValue, aRec.Size);
  37.             aRec.FValue := nil;
  38.             writeln('aRec.Pointer after freeing = ', PtrInt(aRec.FValue))
  39.         end
  40.         else
  41.             writeln('pointer is zero');
  42.     end;
  43.  
  44.     procedure test(var a:TRec);
  45.     var b:TRec;
  46.         i:longint;
  47.  
  48.     begin
  49.         ZeroMemory(@b, sizeof(b));
  50.         initialize(b);
  51.         writeln('b.Pointer = ', PtrInt(b.FValue));
  52.         writeln(b.Size);
  53.         for i:=0 to (b.Size div 4)-1 do
  54.         begin
  55.             b.FValue[i]:=123+i;
  56.         end;
  57.         for i:=0 to (a.Size div 4)-1 do
  58.         begin
  59.             a.FValue[i]:=b.FValue[i];
  60.         end;
  61.     end;
  62.    
  63.  
  64.     var
  65.         t, z, x: TRec;
  66.      
  67.     begin
  68.         test(x);
  69.         initialize(t); //  initialize(t) is not needed but it won't hurt, variables get initialized as soon as a member is accessed
  70.         writeln('t.Size = ', t.Size);
  71.         writeln('t.Pointer = ', PtrInt(t.FValue));
  72.         writeln('valid pointer? ', t.FValue <> nil);
  73.         writeln('t.Size = ', z.Size);
  74.         writeln('t.Size = ', x.Size);
  75.         writeln('x.FValue[0] = ', x.FValue[0]);
  76.         writeln('x.FValue[1] = ', x.FValue[1]);
  77.         writeln('x.FValue[2] = ', x.FValue[2]);
  78.         writeln('x.FValue[',1000-3,'] = ', x.FValue[1000-3]);
  79.         writeln('x.FValue[',1000-2,'] = ', x.FValue[1000-2]);
  80.         writeln('x.FValue[',1000-1,'] = ', x.FValue[1000-1]);
  81.         readln;
  82.     end.
  83.  
« Last Edit: October 14, 2024, 02:04:30 am by srvaldez »

440bx

  • Hero Member
  • *****
  • Posts: 4731
Re: memory management
« Reply #10 on: October 13, 2024, 11:12:09 pm »
variables created inside a procedure don't get initialized
Of course they don't.

You need to initialize local variables.  There are various ways of accomplishing that.  My personal favorite is simple ZeroMemory(@variable, sizeof(variable)), simple and easy.  In a class the initialization is usually done in the constructor or possibly in a method dedicated to initialization (something along the lines of "init".)

As a matter of course and good programming, variables should always be _explicitly_ initialized.

Since I'm mentioning variable initialization, I should mention one way that should _not_ be used to initialize variables and that is: var SomeStructuredVar = ();" the "()" construction more often than not generates absolutely appalling code (learned that the hard way!.)  Use ZeroMemory, simple, easy and, more often than not, quite efficient (for simple types, just assign a constant value.)

HTH.
(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

srvaldez

  • Full Member
  • ***
  • Posts: 116
Re: memory management
« Reply #11 on: October 14, 2024, 01:06:33 am »
thank you 440bx  :)
I edited the post above, it works now
while this works it's not the same as constructors/destructors, any good tutorial on that ?

440bx

  • Hero Member
  • *****
  • Posts: 4731
Re: memory management
« Reply #12 on: October 14, 2024, 01:35:10 am »
thank you 440bx  :)
I edited the post above, it works now
One suggestion, you can tag text as "code=Pascal" instead of just "code".  That will make the post look a lot nicer.

while this works it's not the same as constructors/destructors, any good tutorial on that ?
I don't do OOP stuff therefore I don't know of any tutorials.  Hopefully someone else can help you with that.

(FPC v3.0.4 and Lazarus 1.8.2) or (FPC v3.2.2 and Lazarus v3.2) on Windows 7 SP1 64bit.

srvaldez

  • Full Member
  • ***
  • Posts: 116
Re: memory management
« Reply #13 on: October 14, 2024, 02:03:43 am »
👍😁

Thaddy

  • Hero Member
  • *****
  • Posts: 16168
  • Censorship about opinions does not belong here.
Re: memory management
« Reply #14 on: October 14, 2024, 10:54:03 am »
[edit, later added equality operator, since I also added the copy operator, which implies assignment operator]
[edit 2 added comments ]
The working, leak free version. Do please not call initialize manually, it DOES hurt!:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.     {$mode objfpc}
  3.     {$modeswitch advancedrecords}
  4.  
  5.     {$warn 5089 off} { suppress uninitialized warning }
  6.     {$warn 5090 off} { suppress uninitialized warning }
  7.     {$warn 5092 off} { suppress uninitialized warning }
  8.     {$warn 5025 off} { suppress variable not used warning }
  9.     {$warn 4055 off} { suppress not portable }
  10.     type
  11.       TRec = record
  12.       public
  13.         Value: PLongint;
  14.         Size: PtrUInt; { size of allocated memory }
  15.       strict private
  16.         class operator Initialize(var aRec: TRec);
  17.         class operator Finalize(var aRec: TRec);
  18.         class operator Copy(constref cFrom:Trec;var cTo:Trec);
  19.       public
  20.        { this operator should be public, however: it is not a management operator }
  21.         class operator = (const a,b:TRec):Boolean;
  22.       end;
  23.      
  24.     class operator TRec.Initialize(var aRec: TRec);
  25.     begin
  26.        ReturnNilIfGrowHeapFails := True;
  27.        writeln('TRec.Initialize: before: Value = ',ReturnNilIfGrowHeapFails, 'Value = ', ptrUint(aRec.Value));
  28.        aRec.Size := 1000*4;
  29.        aRec.Value := AllocMem(aRec.Size);
  30.        writeln('TRec.Initialize: after: Value = ',ReturnNilIfGrowHeapFails, 'Value = ', PtrUInt(aRec.Value));
  31.     end;
  32.  
  33.     class operator TRec.Finalize(var aRec: TRec);
  34.     begin
  35.       WriteLn('Just to let you know: I am finalizing..');
  36.       FreeMem(aRec.Value); { make sure memory is free'd! }
  37.       aRec.Value := nil;
  38.       aRec.Size := 0;
  39.       writeln('aRec.Pointer after freeing = ', PtrUInt(aRec.Value));
  40.     end;
  41.  
  42.     class operator TRec.Copy(constref cFrom:Trec;var cTo:TRec);
  43.     begin
  44.       freemem(cTo.Value);                      { calling Freemem on nil is ok }
  45.       cTo.Value := AllocMem(cFrom.Size);       { allocate a new block }
  46.       cTo.Size := cFrom.Size;                  { of the same size as the source }
  47.       move(cFrom.Value^,cTo.Value^,cFrom.Size);{ only then copy the content }
  48.       writeln('Copied on assignment :=' );
  49.     end;
  50.    
  51.     class operator TRec.= (const a,b:TRec):Boolean;
  52.     begin
  53.       { the records have the same content if the size is equal and the memory content is equal }
  54.       Result := (a.size = b.size) and (CompareByte(a.value^,b.value^,a.size) = 0);
  55.     end;
  56.  
  57.     procedure test(var a:TRec);
  58.     var b:TRec;
  59.         i:longint;
  60.     begin
  61.         writeln('b.Pointer = ', PtrUInt(b.Value));
  62.         writeln(b.Size);
  63.         for i:=0 to (b.Size div 4)-1 do
  64.         begin
  65.             b.Value[i]:=123+i;
  66.         end;   
  67.         a:=b; { assignment is copy on write, local var b is finalized on procedure exit. }
  68.     end;
  69.    
  70.  
  71.     var
  72.         t, z, x: TRec;    
  73.     begin
  74.       test(x);
  75.       writeln('t.Size = ', t.Size);
  76.       writeln('t.Pointer = ', PtrUInt(t.Value));
  77.       writeln('valid pointer? ', t.Value <> nil);
  78.       writeln('z.Size = ', z.Size);
  79.       writeln('x.Size = ', x.Size);
  80.       writeln('x.Value[0] = ', x.Value[0]);
  81.       writeln('x.Value[1] = ', x.Value[1]);
  82.       writeln('x.Value[2] = ', x.Value[2]);
  83.       writeln('x.Value[',1000-3,'] = ', x.Value[1000-3]);
  84.       writeln('x.Value[',1000-2,'] = ', x.Value[1000-2]);
  85.       writeln('x.Value[',1000-1,'] = ', x.Value[1000-1]);
  86.       t:=x; { assignment is copy on write }
  87.       writeln('Is the content of t equal to that of x after assignment? ',t = x);
  88.       writeln('t.Value[',1000-3,'] = ', t.Value[1000-3]);
  89.       writeln('t.Value[',1000-2,'] = ', t.Value[1000-2]);
  90.       writeln('t.Value[',1000-1,'] = ', t.Value[1000-1]);        
  91.       readln;
  92.     end.
As you can see I implemented the copy operator to solve most of your problems and I changed the way the memory is used and initialized. Again, do not call initialize manually, that will cause leaks.
That's why I made the management operators strict private: now you can't...  :o
Management operators should always be strict private.

The code is also considerably less complicated.
« Last Edit: October 14, 2024, 01:46:14 pm by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

 

TinyPortal © 2005-2018