Recent

Author Topic: Variable initialization  (Read 4192 times)

alpine

  • Hero Member
  • *****
  • Posts: 1412
Re: Variable initialization
« Reply #30 on: February 28, 2025, 01:31:51 pm »
Code: Pascal  [Select][+][-]
  1. c := TSomeObject.Create();  // First creation
  2. c := TSomeObject.Create();  // Second creation without freeing the first
  3.  

To pour some oil on the fire... is also valid to:
Code: Pascal  [Select][+][-]
  1. c := TSomeObject.Create();  // First creation
  2. //...
  3. c.Create();  // overhaul
  4.  
and in certain cases even useful
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

TRon

  • Hero Member
  • *****
  • Posts: 4377
Re: Variable initialization
« Reply #31 on: February 28, 2025, 03:23:52 pm »
How is this any different?! the contructor is called twice?
Objects/classes can be created as many times as wanted but once instantiated re-using the variable that hold the reference to that instantiation is simply gone (and with that no way to retrieve back the memory it occupies).

In the example, the variable containing the reference to the instance of the class is added to a list (and doing so before the next time the class is instantiated).

We do not know what the list does other than it contains the reference to all the instantiated classes that were added to it. It could be the list automatically takes care of destroying the instances once the list is done with them or that you can reference the instances of the classes using for example a items or objects property but the fact is, they are stored and can be retrieved later on.
Today is tomorrow's yesterday.

dbannon

  • Hero Member
  • *****
  • Posts: 3687
    • tomboy-ng, a rewrite of the classic Tomboy
Re: Variable initialization
« Reply #32 on: March 02, 2025, 02:00:35 am »
What Ron is saying is memory leak !  And to prove it, I added a couple of lines to the code I was looking at, no surprise silvercoder70 leaks. But, big surprise, Alpine's version does not ???

Code: Pascal  [Select][+][-]
  1. var
  2.   ClientEventLoop: TEventLoop;            // in fpasync
  3. begin
  4.     ClientEventLoop := TEventLoop.Create;  // First create
  5.     ClientEventLoop := TEventLoop.Create;  // Makes for memory leak
  6.     ClientEventLoop.Create;                //  Does NOT leak ????
  7.     ....
  8.     ClientEventLoop.Free;

Maybe it the nature of the class I picked ?

Davo

Edit: added a free in case some pedant complains.
« Last Edit: March 02, 2025, 02:04:25 am by dbannon »
Lazarus 3, Linux (and reluctantly Win10/11, OSX Monterey)
My Project - https://github.com/tomboy-notes/tomboy-ng and my github - https://github.com/davidbannon

Warfley

  • Hero Member
  • *****
  • Posts: 2038
Re: Variable initialization
« Reply #33 on: March 02, 2025, 09:28:08 am »
When you call the constructor from the instance than you only call it as a regular function (think constructor overloading, you need a way that one constructor can call another). But when you can it on the class than it does the whole allocation and initialization of memory.

There is one really neat hack about the constructor, because the constructor is the only virtual function that's allowed to change the signature, the return type to be precise, as it always returns the type on which it is defined.
So one very useful hack with the constructor is for manual reference counting:

Code: Pascal  [Select][+][-]
  1. constructor AddRef; virtual;
  2. begin
  3.   Inc(RefCount);
  4. end;
You can define this in a parent class, and use it in all subclasses to have a function that increments the RefCount and return self in the correct type

Thaddy

  • Hero Member
  • *****
  • Posts: 18729
  • To Europe: simply sell USA bonds: dollar collapses
Re: Variable initialization
« Reply #34 on: March 02, 2025, 03:25:42 pm »
I don't understand Alpine's last contribution.
As TRon explained a create - for class, not record - without assignment to a variable will invariably leak in the normal process of things. There are some tricks, though, to avoid that using a different memory model, like COM or a garbage collector memory manager. Both are not recommended.
If Europe sells their USA bonds the USD will collapse. Europe can affort that given average state debts. The USA can't affort that. Just an advice...

alpine

  • Hero Member
  • *****
  • Posts: 1412
Re: Variable initialization
« Reply #35 on: March 02, 2025, 05:09:45 pm »
I don't understand Alpine's last contribution.
I just wanted to remind that besides the canonical usage:
Code: Pascal  [Select][+][-]
  1. c := TSomeObject.Create;
in which the memory for the instance is allocated on the heap, filled with zeroes, VMT initialized (TSomeObject.NewInstance/InitInstance), and finally, the constructor is called and reference returned,

It is also possible to call the constructor method on an already pre-made instance:
Code: Pascal  [Select][+][-]
  1. c.Create;
in which case it will be invoked as a regular method (as Warfley noted). This can be done in scenarios where we want to reuse the old instances and not to re-allocate them all over again to protect against heap fragmentation for example. In this case there should be no presumption that all fields are zeroed and the constructor must initialize (it)self to the initial state.
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Raskaton

  • New Member
  • *
  • Posts: 22
Re: Variable initialization
« Reply #36 on: March 08, 2025, 10:16:20 pm »
If some problems with understanding classes and passing 'by reference' fpc have old records with Default, or Advanced records with Initialize.
Code: Pascal  [Select][+][-]
  1. program InitAdvRecords;
  2.  
  3. {$Mode objfpc}{$H+}
  4. {$ModeSwitch advancedrecords}
  5.  
  6. type
  7.  
  8.   TRec = record
  9.     B: Boolean;
  10.     S: ShortString;
  11.     class operator Initialize(var Instance: TRec);
  12.   end;
  13.  
  14. class operator TRec.Initialize(var Instance: TRec);
  15. begin
  16.   Instance.B := True;
  17.   Instance.S := 'unknown';
  18. end;
  19.  
  20. var
  21.   Rec: TRec; //at this moment Initialize called
  22. begin
  23.   //Normally we need manualy init record or compiler show warning:
  24.   //Rec := Default(TRec);
  25.   //TRec record have Initialize, but still have warning.
  26.   WriteLn('Rec.B = ', Rec.B); //print True
  27.   WriteLn('Rec.S = ', Rec.S); //print 'unknown'
  28.  
  29.   Rec := Default(TRec); //init ordinal fields by zeros, nils, #0 and ''
  30.   WriteLn('Rec.B = ', Rec.B); //print False
  31.   WriteLn('Rec.S = ', Rec.S); //print ''
  32. end.
  33.  
About Default booleans and all ordinal types:
https://www.freepascal.org/docs-html/current/ref/ref.html#QQ2-56-83

InitializeFinalize can be used for creating personal managed types, that pre-initialize some array fields or nested records with arrays/objects.

MarkMLl

  • Hero Member
  • *****
  • Posts: 8533
Re: Variable initialization
« Reply #37 on: March 08, 2025, 10:49:24 pm »
It is also possible to call the constructor method on an already pre-made instance:

What are the semantics if a constructor (ab)used in this fashion fails?

MarkMLl
MT+86 & Turbo Pascal v1 on CCP/M-86, multitasking with LAN & graphics in 128Kb.
Logitech, TopSpeed & FTL Modula-2 on bare metal (Z80, '286 protected mode).
Pet hate: people who boast about the size and sophistication of their computer.
GitHub repositories: https://github.com/MarkMLl?tab=repositories

AlanTheBeast

  • Sr. Member
  • ****
  • Posts: 407
  • My software never cras....
Re: Variable initialization
« Reply #38 on: March 09, 2025, 08:02:19 pm »
Have this thing on my mind for a while... does the compiler initializes variables with some defaults ?

I wouldn't need to know all. I just need to know boolean. Is there a guarantee every boolean is initialized with False ?

Standard Pascal says make no assumptions, IIRC.  And it is good practice to do so.

As others note, "new pascal" allows init in the declaration.        RichardIsRich : boolean = false;

Testing the state of various vars might show them to be in fact 0.  That might be the OS clearing globals.  Maybe.

Some experimentation might show that local vars show remnants of what a prior procedure/function left on the stack.

The code below has the value pi appear for x on the 2nd call to STB;   Some value from an intervening process appears in a;
(Note: compile w/o optimization to be sure the vars are not register allocated - though similar behaviour might occur there too).


Quote
Proc B
a= 29632
x=    0.00000
Size of var a: 2
ProcA
Proc B
a= 24
x=    3.14159
Size of var a: 2
Code: Pascal  [Select][+][-]
  1. Procedure StackClear;
  2. VAR
  3.         a, b, c: longword;
  4. BEGIN
  5.         a := 0;
  6.         b := 0;
  7.         c := 0;
  8. END;
  9.  
  10. Procedure STA;
  11. VAR
  12.         a: word;
  13.         x: double;
  14. BEGIN
  15.         writeln ('ProcA');
  16.         a := 24;
  17.         x := 4*arctan(1);
  18. END;
  19.  
  20. Procedure STB;
  21. VAR
  22.         a: word;
  23.         x: double;
  24. BEGIN
  25.         writeln ('Proc B');
  26.         writeln ('a= ',a);
  27.         writeln ('x= ',x:10:5);
  28.         Writeln('Size of var a: ',Sizeof(a));
  29. END;
  30.  
  31. Procedure StackTrash;
  32. BEGIN
  33.         StackClear;
  34.         STB;
  35.         STA;
  36.         STB;
  37. END;





Everyone talks about the weather but nobody does anything about it.
..Samuel Clemens.

alpine

  • Hero Member
  • *****
  • Posts: 1412
Re: Variable initialization
« Reply #39 on: March 09, 2025, 10:31:24 pm »
It is also possible to call the constructor method on an already pre-made instance:

What are the semantics if a constructor (ab)used in this fashion fails?

MarkMLl
Good point! The failed constructor will trigger the destructor automatically. I'm not sure what the consequences will be.
But IMO this isn't that important, because writing potentially failing constructors is not a good idea in general. 
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Khrys

  • Sr. Member
  • ****
  • Posts: 391
Re: Variable initialization
« Reply #40 on: March 10, 2025, 10:44:49 am »
It is also possible to call the constructor method on an already pre-made instance:

What are the semantics if a constructor (ab)used in this fashion fails?

MarkMLl
Good point! The failed constructor will trigger the destructor automatically. I'm not sure what the consequences will be.

Take the following example compiled using FPC 3.2.2 with  -O3  on x86_64 Linux:

Code: Pascal  [Select][+][-]
  1. type
  2.   TCounter = class
  3.     I: Cardinal;
  4.     constructor Create();
  5.     destructor Destroy(); override;
  6.   end;
  7.  
  8. constructor TCounter.Create();
  9. begin
  10.   I := $DEADBEEF;
  11. end;
  12.  
  13. destructor TCounter.Destroy();
  14. begin
  15.   inherited Destroy();
  16. end;

Constructor:

Code: ASM  [Select][+][-]
  1. TCounter.Create({rdi_class: PVMT or TCounter; rsi_flag: SizeInt}):
  2.  
  3.     # allocate space for local variables
  4.  
  5.     # [rsp]: SizeInt = rsi_flag
  6.     # [rsp+8]: PVMT or TCounter = VMT or instance
  7.     # [rsp+16]: SizeInt = destructor flag
  8.  
  9.     # [rsp+24]: TExceptAddr = constructor try..finally frame (24 bytes)
  10.     #   [rsp+24]: pjmp_buf = buf
  11.     #   [rsp+32]: PExceptAddr = next
  12.     #   [rsp+40]: LongInt = frametype
  13.  
  14.     # [rsp+48]: jmp_buf = constructor try..finally target (64 bytes on Linux)
  15.     #   ... registers
  16.  
  17.     # [rsp+112]: SizeInt = constructor setjmp result
  18.  
  19.     # [rsp+120]: TExceptAddr = destructor try..finally frame (24 bytes)
  20.     #   [rsp+120]: pjmp_buf = buf
  21.     #   [rsp+128]: PExceptAddr = next
  22.     #   [rsp+136]: LongInt = frametype
  23.  
  24.     # [rsp+144]: jmp_buf = destructor try..finally target (64 bytes on Linux)
  25.     #   ... registers
  26.  
  27.     # [rsp+208]: SizeInt = destructor setjmp result
  28.     lea rsp, [rsp-216]
  29.  
  30.     # store arguments
  31.     mov [rsp+8], rdi    # TCounter VMT or instance
  32.     mov [rsp], rsi      # flag
  33.  
  34.     # check if allocation needed (flag = 1)
  35.     cmp rsi, 1
  36.     jne .no_alloc
  37.  
  38.         # allocate new instance
  39.         mov rax, [rsp+8]    # VMT of TCounter
  40.         mov rdx, [rsp+8]
  41.         mov rdi, rax        # TCounter as self
  42.         call [rdx+104]      # TCounter.NewInstance()
  43.  
  44.         mov [rsp+8], rax    # store instance
  45.  
  46. .no_alloc:
  47.  
  48.     # run user-defined constructor code if instance not nil
  49.     cmp [rsp+8], 0
  50.     je .return
  51.  
  52.         # set up try..finally
  53.         lea rdx, [rsp+24]           # new address
  54.         lea rsi, [rsp+48]           # jump buffer
  55.         mov edi, 1                  # exception frame type
  56.         call fpc_pushexceptaddr
  57.  
  58.         # set longjmp target
  59.         mov rdi, rax                # jump buffer
  60.         call fpc_setjmp
  61.  
  62.         # get longjmp result linked to jump buffer
  63.         movsxd rdx, eax
  64.         mov [rsp+112], rdx
  65.  
  66.         # proceed only after original setjmp invocation
  67.         test eax, eax
  68.         jne .finally
  69.  
  70.             # prevent TCounter.BeforeDestruction from running
  71.             mov [rsp+16], -1
  72.  
  73.             # user-defined constructor code
  74.             mov rax, [rsp+8]
  75.             mov [rax+8], DEADBEEFh
  76.  
  77.             # allow TCounter.BeforeDestruction to run
  78.             # reached only if user-defined constructor code did not fail
  79.             mov [rsp+16], 1
  80.  
  81.             # check again if instance is not nil
  82.             cmp [rsp+8], 0
  83.             je .finally
  84.  
  85.                 # check if this is the first constructor invocation
  86.                 cmp [rsp], 0
  87.                 je .finally
  88.  
  89.                     # run post-construction handler
  90.                     mov rdi, [rsp+8]    # instance as self
  91.                     mov rax, [rsp+8]
  92.                     mov rax, [rax]      # VMT of instance
  93.                     call [rax+136]      # TCounter.AfterConstruction()
  94.  
  95. .finally:
  96.  
  97.     # clean up try..finally
  98.     call fpc_popaddrstack
  99.  
  100.     # run destructor if an exception was raised
  101.     mov rax, [rsp+112]
  102.     test rax, rax
  103.     je .return
  104.  
  105.         # set up try..except
  106.         lea rdx, [rsp+120]          # new address
  107.         lea rsi, [rsp+144]          # jump buffer
  108.         mov edi, 1                  # exception frame type
  109.         call fpc_pushexceptaddr
  110.  
  111.         # set longjmp target
  112.         mov rdi, rax                # jump buffer
  113.         call fpc_setjmp
  114.  
  115.         # get longjmp result linked to jump buffer
  116.         movsxd rdx, eax
  117.         mov [rsp+208], rdx
  118.  
  119.         # proceed only after original setjmp invocation
  120.         test eax, eax
  121.         jne .finally_destructor
  122.  
  123.             # check if this is the first constructor invocation
  124.             cmp [rsp], 0
  125.             je .no_dealloc
  126.  
  127.                 # run destructor
  128.                 mov rsi, [rsp+16]   # destructor flag
  129.                 mov rdi, [rsp+8]    # instance as self
  130.                 mov rax, [rsp+8]
  131.                 mov rax, [rax]      # VMT of instance
  132.                 call [rax+96]       # TCounter.Destroy()
  133. .no_dealloc:
  134.  
  135.             # clean up try..finally and re-raise original constructor exception
  136.             call fpc_popaddrstack
  137.             call fpc_reraise
  138.  
  139. .finally_destructor:
  140.  
  141.         # clean up try..finally
  142.         call fpc_popaddrstack
  143.  
  144.         # check if destructor itself raised an exception
  145.         mov rax, [rsp+208]
  146.         test rax, rax
  147.         je .no_nested_exception
  148.  
  149.             # re-raise nested destructor exception
  150.             call fpc_raise_nested
  151.  
  152. .no_nested_exception:
  153.  
  154.         # clean up potentially re-raised FPC exception objects
  155.         call fpc_doneexception
  156.  
  157. .return:
  158.  
  159.     # deallocate locals and return instance
  160.     mov rax, [rsp+8]        # instance
  161.     lea rsp, [rsp+216]
  162.     ret

Destructor:

Code: ASM  [Select][+][-]
  1. TCounter.Destroy({rdi_self: TCounter; rsi_flag: SizeInt}):
  2.  
  3.     # save non-volatile registers and align stack
  4.     push rbx
  5.     push r12
  6.     lea rsp, [rsp-8]
  7.  
  8.     # store arguments in non-volatile registers
  9.     mov rbx, rdi        # self (instance)
  10.     mov r12, rsi        # flag
  11.  
  12.     # check if flag > 0
  13.     test r12, r12
  14.     jng .no_before_destroy
  15.  
  16.         # run pre-destruction handler
  17.         mov rdi, rbx    # self
  18.         mov rax, [rbx]  # VMT of instance
  19.         call [rax+144]  # TCounter.BeforeDestruction()
  20.  
  21. .no_before_destroy:
  22.  
  23.     # user-defined destructor code
  24.     mov rdi, rbx            # self
  25.     xor esi, esi            # flag = 0
  26.     call TObject.Destroy()
  27.  
  28.     # check if instance is not nil
  29.     test rbx, rbx
  30.     je .return
  31.  
  32.         # check if flag <> 0
  33.         test r12, r12
  34.         je .return
  35.  
  36.             # free instance
  37.             mov rdi, rbx    # self
  38.             mov rax, [rbx]  # VMT of instance
  39.             call [rax+112]  # TCounter.FreeInstance()
  40.  
  41. .return:
  42.  
  43.     # unalign stack and restore volatile registers
  44.     lea rsp, [rsp+8]
  45.     pop r12
  46.     pop rbx
  47.     ret

The destructor's flag parameter determines what actions it performs (in this order):
  • flag > 0:  call  BeforeDestruction
  • always:  run user-defined code
  • flag <> 0:  call  FreeInstance

In the constructor's implicit  try-finally  block, the destructor flag is set to  -1  before running the user-defined constructor code, after which it is set to  1.
This means that a failing constructor always causes the instance to be freed, regardless of whether it was called on an already existing instance. The only difference is that the allocation step is skipped.



For completeness, here's how the constructor's flag affects its actions:
  • flag = 1:  call  NewInstance
  • self <> nil:
        run user-defined code
        exception and flag <> 0:  call destructor
        self <> nil and flag <> 0:  call  AfterConstruction

Calling inherited destructors and constructors sets the respective flag to  0.

TObject.Create()  - VMT passed, flag =  1
Instance.Create()  - instance passed, flag =  -1

 

TinyPortal © 2005-2018