Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-pascal / 2023-12

Double finalize

7 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Hairy Pixels

I feel like this was addressed but I couldn't find any references. The program below prints the following:

Initialize
Create
Finalize
Finalize

Why is Finalize called twice? I thought those were supposed to be called in pairs with initialize?

==================

{$mode objfpc}
{$modeswitch advancedrecords}
program test;
type
  TManagedObject = record
    class function Create: TManagedObject; static;
    class operator Finalize(var self: TManagedObject); inline;
    class operator Initialize(var self: TManagedObject); inline;
  end;
class function TManagedObject.Create: TManagedObject;
begin
  writeln('Create');
end;
class operator TManagedObject.Finalize(var self: TManagedObject);
begin
  writeln('Finalize');
end;
class operator TManagedObject.Initialize(var self: TManagedObject);
begin
  writeln('Initialize');
end;
begin
  TManagedObject.Create;
end.

Regards,
Ryan Joseph

Michael Van Canneyt
to Hairy Pixels
Hairy Pixels wrote 8 lines
I feel like this was addressed but I couldn't find any references. The program below prints the following: Initialize Create Finalize Finalize Why is Finalize called twice? I thought those were supposed to be called in pairs with initialize?

If you look at the generated code, you see that there is an implicit try/finally block and the finally block does a finalize.

call fpc_pushexceptaddr
movq %rax,%rdi
call fpc_setjmp
movl %eax,-92(%rbp)
cmpl $0,%eax
jne .Lj10
leaq -96(%rbp),%rdi
call P$TEST$_$TMANAGEDOBJECT_$__$$_CREATE$$TMANAGEDOBJECT movq $INIT_$P$TEST_$$_TMANAGEDOBJECT,%rsi
leaq -96(%rbp),%rdi
call fpc_finalize
.Lj10:
call fpc_popaddrstack
movq $INIT_$P$TEST_$$_TMANAGEDOBJECT,%rsi
leaq -96(%rbp),%rdi
call fpc_finalize
movl -92(%rbp),%eax

Michael.

Hairy Pixels
to Michael Van Canneyt
On Dec 21, 2023, at 1:53 AM, Michael Van Canneyt via fpc-pascal wrote: If you look at the generated code, you see that there is an implicit try/finally block and the finally block does a finalize.

Maybe I misunderstood but I thought they were supposed to be balanced with init calls. Is it the design of the compiler to allow multiple finalize calls and have the user keep track of it the underlying structure is really finalized or not? If you were doing things like deleting memory in the finalizer you would have a double free happen here.

Regards,
Ryan Joseph

Hairy Pixels
to Hairy Pixels
On Dec 21, 2023, at 6:11 AM, Hairy Pixels wrote: Maybe I misunderstood but I thought they were supposed to be balanced with init calls. Is it the design of the compiler to allow multiple finalize calls and have the user keep track of it the underlying structure is really finalized or not? If you were doing things like deleting memory in the finalizer you would have a double free happen here.

Here's another example of this. If I use an array Finalize is called 4 times but Initialize is never called, unless you assign a record to the array and then it's called once.

This makes even less sense. What's the idea behind this behavior? I would expect the calls to balanced as this feature is intended for reference counting I thought.

==============================

{$mode objfpc}
{$modeswitch advancedrecords}
program test;
type
 TManagedObject = record
   x: integer;
   class operator Finalize(var self: TManagedObject); inline;
   class operator Initialize(var self: TManagedObject); inline;
 end;
class operator TManagedObject.Finalize(var self: TManagedObject);
begin
 writeln('Finalize');
end;
class operator TManagedObject.Initialize(var self: TManagedObject);
begin
 writeln('Initialize');
end;
var
 a: array[0..3] of TManagedObject;
begin
 a[0].x := 1;
 a[1].x := 1;
 a[2].x := 1;
 a[3].x := 1;
end.

Regards,
Ryan Joseph

Nikolay Nikolov
to Hairy Pixels: Maybe I misunderstood but I thought they were supposed to be balanced with init calls. Is…
Hairy Pixels wrote 6 lines
> On Dec 21, 2023, at 1:53 AM, Michael Van Canneyt via fpc-pascal wrote: > > If you look at the generated code, you see that there is an implicit try/finally block > and the finally block does a finalize. Maybe I misunderstood but I thought they were supposed to be balanced with init calls. Is it the design of the compiler to allow multiple finalize calls and have the user keep track of it the underlying structure is really finalized or not? If you were doing things like deleting memory in the finalizer you would have a double free happen here.

Note that you should also add an 'AddRef' and 'Copy' management operators and implement these as well. However, I think it's an FPC bug that Initialize is called twice in this case. I could be wrong, though.

If you assign the result to a local variable, i.e.:

var
   a: TManagedObject;
begin
   a := TManagedObject.Create;
end.

You get the following output:

Initialize
Initialize
Create
Copy
Finalize
Finalize

So, I think there's a bug in FPC that finalize is called twice when the return value is discarded.

Nikolay

Nikolay Nikolov
to Hairy Pixels: Here's another example of this. If I use an array Finalize is called 4 times but …
Hairy Pixels wrote 7 lines
> On Dec 21, 2023, at 6:11 AM, Hairy Pixels wrote: > > Maybe I misunderstood but I thought they were supposed to be balanced with init calls. Is it the design of the compiler to allow multiple finalize calls and have the user keep track of it the underlying structure is really finalized or not? If you were doing things like deleting memory in the finalizer you would have a double free happen here. Here's another example of this. If I use an array Finalize is called 4 times but Initialize is never called, unless you assign a record to the array and then it's called once. This makes even less sense. What's the idea behind this behavior? I would expect the calls to balanced as this feature is intended for reference counting I thought.

Even though it should be possible to implement reference counting, I don't think that's the management operators' primary intended use. Interfaces and creating a class that inherits from TInterfacedObject is the traditional Delphi way to implement reference counting. I tend to think of management operators as a way to do a C++ way of managed objects, passed by value. That includes not just Initialize and Finalize (in C++ these are constructors and destructors), but also Copy as well (in C++, this is done by the copy constructor and the assignment operator).

If I wanted to use reference counting, I would still use interfaces and TInterfacedObject.

If I wanted to create a new value type that uses memory on the heap, I would implement not just Initialize and Finalize, but also Copy and probably AddRef (although I don't know when AddRef is used).

However, I think of management operators as a newer and less stable feature, so I think that double Finalize call is a bug, that happens when a function result is discarded, which is allowed in the extended syntax {$X+} mode. Management operators were, unfortunately, added by a developer, who had disagreements with the other core FPC developers and started an FPC fork, called NewPascal. So it's possible some of the features he added were not as stable or as finished as they should be.

That's why I would use interfaces and TInterfacedObject if I wanted just reference counting.

Btw, I used management operators in my Unicode-enabled version of the KVM units and Free Vision:

https://wiki.freepascal.org/Free_Vision#Unicode_version

See the TEnhancedVideoCell record in:

https://gitlab.com/freepascal.org/fpc/source/-/blob/main/packages/rtl-console/src/inc/videoh.inc

It enables us to save memory when a character cell is represented by a single Unicode code point in the BMP range, but still support complex graphemes (which can contain a sequence of unicode code points - combining characters, etc.) allocated on the heap when necessary.

So, I don't have hesitance in using them when they are the best choice, it's just that I consider them less stable and expect bugs. I just don't think they're the best for implementing reference counting.

Nikolay

Show quoted text 41 lines
============================== {$mode objfpc} {$modeswitch advancedrecords} program test; type TManagedObject = record x: integer; class operator Finalize(var self: TManagedObject); inline; class operator Initialize(var self: TManagedObject); inline; end; class operator TManagedObject.Finalize(var self: TManagedObject); begin writeln('Finalize'); end; class operator TManagedObject.Initialize(var self: TManagedObject); begin writeln('Initialize'); end; var a: array[0..3] of TManagedObject; begin a[0].x := 1; a[1].x := 1; a[2].x := 1; a[3].x := 1; end. Regards, Ryan Joseph _______________________________________________ fpc-pascal maillist - https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-pascal
Hairy Pixels
to Hairy Pixels: Here's another example of this. If I use an array Finalize is called 4 times but …
Show quoted text 5 lines
On Dec 21, 2023, at 12:06 PM, Hairy Pixels wrote: Here's another example of this. If I use an array Finalize is called 4 times but Initialize is never called, unless you assign a record to the array and then it's called once. This makes even less sense. What's the idea behind this behavior? I would expect the calls to balanced as this feature is intended for reference counting I thought.

I had some time to think about this more and I think it does make sense actually. Initializing each element wouldn't make sense because there was no assignment made but if an assignment were made it would need to finalize but during compile time there's no way to know which elements were assigned to.

Regards,
Ryan Joseph