Recent

Author Topic: Record strangeness...  (Read 1409 times)

JdeHaan

  • Full Member
  • ***
  • Posts: 150
Record strangeness...
« on: June 11, 2024, 05:48:49 pm »
Hi,

In a large project I have a record definition, like:

Code: Pascal  [Select][+][-]
  1. type
  2.  
  3. PMyRec = ^TMyRec;
  4. TMyRec = record
  5.   Enclosing: PMyRec;
  6.   Name: String;
  7.   constructor Create(const aName: String);
  8. end;
  9.  
  10. var
  11.   Current: PMyRec; // gets a value later on
  12.  
  13. constructor TMyRec.Create(const aName: String);
  14. begin
  15.   Enclosing := Current;
  16.   Name := aName;
  17.   Current := @Self;
  18. end;
  19.  
In the code I initialize a variable of type TMyRec as:

Code: Pascal  [Select][+][-]
  1. MyRec := TMyRec.Create('ABC');

However, this didn’t work and I got access violation errors.
When I changed the constructor into a normal procedure, like so:

Code: Pascal  [Select][+][-]
  1. procedure Init(const aName: String);
  2.  
  3. procedure TMyRec.Init(const aName: String);
  4. begin
  5.   Enclosing := Current;
  6.   Name := aName;
  7.   Current := @Self;
  8. end;
  9.  
and used it like this:

Code: Pascal  [Select][+][-]
  1. MyRec.Init('ABC');

This runs without any problems.

How can this be explained?


Delphi mode / trunk 4 weeks ago / MacOs Sonoma 14.5

Thaddy

  • Hero Member
  • *****
  • Posts: 16168
  • Censorship about opinions does not belong here.
Re: Record strangeness...
« Reply #1 on: June 11, 2024, 06:21:10 pm »
It works as expected:
Code: Pascal  [Select][+][-]
  1. {$mode objfpc}{$modeswitch advancedrecords}{$apptype console}
  2. type
  3.  
  4. PMyRec = ^TMyRec;
  5. TMyRec = record
  6.   Enclosing: PMyRec;
  7.   Name: String;
  8.   constructor Create(const aName: String);
  9. end;
  10.  
  11. var
  12.   Current: PMyRec; // gets a value later on
  13.  
  14. constructor TMyRec.Create(const aName: String);
  15. begin
  16.   Enclosing := Current;
  17.   Name := aName;
  18.   Current := @Self;
  19. end;
  20.  
  21. var
  22.  MyRec:TMyrec;
  23.  begin
  24.   MyRec := TMyRec.Create('ABC');
  25.   Writeln(MyRec.Name)
  26.   Readln;//for windows
  27. end.
You probably forgot the modeswitch....
Tested on Linux64 and Windows 11.

So that is not where your error comes from, it comes from mishandling the pointers to your record...<sigh>,  :D
Btw, where is init defined???? Is it the method or the procedure?

I will do that for you later.

I am only a human debugger, but results as close as AI.. :o
« Last Edit: June 11, 2024, 07:10:17 pm by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

JdeHaan

  • Full Member
  • ***
  • Posts: 150
Re: Record strangeness...
« Reply #2 on: June 11, 2024, 06:56:04 pm »
As mentioned I use mode Delphi, so don't need the modeswitch.
The variable Current^ is used extensively in my code, so I thought maybe the assignment to @Self in the constructor code was the problem. It's just strange that with the procedure Init and the same code it works but using a constructor it doesn't.

And init is in the record, just replace the constructor with the procedure:

Code: Pascal  [Select][+][-]
  1. PMyRec = ^TMyRec;
  2. TMyRec = record
  3.   Enclosing: PMyRec;
  4.   Name: String;
  5.   procedure Init(const aName: String);
  6. end;
« Last Edit: June 11, 2024, 06:58:47 pm by JdeHaan »

Thaddy

  • Hero Member
  • *****
  • Posts: 16168
  • Censorship about opinions does not belong here.
Re: Record strangeness...
« Reply #3 on: June 11, 2024, 07:14:38 pm »
That is not where the error comes from. That part of the initial code is OK (See my proof).
If I smell bad code it usually is bad code and that includes my own code.

440bx

  • Hero Member
  • *****
  • Posts: 4732
Re: Record strangeness...
« Reply #4 on: June 11, 2024, 07:15:23 pm »
This runs without any problems.

How can this be explained?
At first sight it looks like the only explanation is that there is a bug in how constructors are implemented in advanced records.

this other thread points out a similar problem,
https://forum.lazarus.freepascal.org/index.php/topic,50112.0.html
the two might be related.

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

Thaddy

  • Hero Member
  • *****
  • Posts: 16168
  • Censorship about opinions does not belong here.
Re: Record strangeness...
« Reply #5 on: June 11, 2024, 07:18:13 pm »
At first sight it looks like the only explanation is that there is a bug in how constructors are implemented in advanced records.
No. He needs to show more code on how he uses it.
His initial code simply works. There is no bug in the implementation. Period.  The bug is his.
Show more code.
« Last Edit: June 11, 2024, 07:19:46 pm by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

JdeHaan

  • Full Member
  • ***
  • Posts: 150
Re: Record strangeness...
« Reply #6 on: June 11, 2024, 07:40:51 pm »
It's a compiler project with multiple units, incl a byte code generator and a VM - 1000s of code lines.
Believe me the only change I made was replacing the use of the record constructor by the Init procedure...

BTW, I am planning to publish the code and describing document on GitHub soon

Thaddy

  • Hero Member
  • *****
  • Posts: 16168
  • Censorship about opinions does not belong here.
Re: Record strangeness...
« Reply #7 on: June 11, 2024, 08:28:38 pm »
You are looking at the wrong spot, because that part of your code was correct.
As is proven.
If I smell bad code it usually is bad code and that includes my own code.

Warfley

  • Hero Member
  • *****
  • Posts: 1751
Re: Record strangeness...
« Reply #8 on: June 11, 2024, 09:21:28 pm »
Record constructors are weird:
Code: Pascal  [Select][+][-]
  1. type
  2.   TTest = record
  3.     Dummy: Integer;
  4.   class operator Initialize(var rec: TTest);
  5.   class operator Finalize(var rec: TTest);
  6.   class operator Copy(constref src: TTest; var dst: TTest);
  7.   class operator AddRef(var rec: TTest);
  8.  
  9.   constructor Creat(AInteger: Integer);
  10.   end;
  11.  
  12. class operator TTest.Initialize(var rec: TTest);
  13. begin
  14.   WriteLn('Initialize at ', IntPtr(@rec));
  15. end;
  16.  
  17. class operator TTest.Finalize(var rec: TTest);
  18. begin
  19.   WriteLn('Finalize at ', IntPtr(@rec));
  20. end;
  21.  
  22. class operator TTest.Copy(constref src: TTest; var dst: TTest);
  23. begin
  24.   WriteLn('Copy ', IntPtr(@src), ' to ', IntPtr(@dst));
  25. end;
  26.  
  27. class operator TTest.AddRef(var rec: TTest);
  28. begin
  29.   WriteLn('AddRef at ', IntPtr(@rec));
  30. end;
  31.  
  32. constructor TTest.Creat(AInteger: Integer);
  33. begin
  34.   WriteLn('Constructor on ', IntPtr(@Self));
  35.   Dummy := AInteger;
  36. end;
  37.  
  38. procedure Test;
  39. var
  40.   rec: TTest;
  41. begin
  42.   WriteLn('Rec at ', IntPtr(@rec));
  43.   rec := TTest.Creat(42);
  44. end;

Result:
Code: Text  [Select][+][-]
  1. Initialize at 20971052
  2. Initialize at 20971048
  3. Rec at 20971052
  4. Finalize at 20971048
  5. Constructor on 20971048
  6. Copy 20971048 to 20971052
  7. Finalize at 20971052
  8. Finalize at 20971048
As you can see (except for the buggy first finalize), the constructor will be called on a temporary object, which will then be copied over to the actual variable. This means that when you take @Self in the constructor it will point to a temporary object which will be destroyed after it was copied over to the final destination

JdeHaan

  • Full Member
  • ***
  • Posts: 150
Re: Record strangeness...
« Reply #9 on: June 11, 2024, 09:49:35 pm »
Thanks Warfley, that makes sense. So I can't use the constructor in this case.

ASerge

  • Hero Member
  • *****
  • Posts: 2337
Re: Record strangeness...
« Reply #10 on: June 12, 2024, 02:32:09 am »
Record constructors are weird:
Not weird. The original code is weird.
Code: Pascal  [Select][+][-]
  1. MyRec := TMyRec.Create('ABC');
Not
Code: Pascal  [Select][+][-]
  1. MyRec.Create('ABC');
That is, the created new temporary variable is copied to MyRec. But inside the constructor, the address of the temporary variable is saved.

JdeHaan

  • Full Member
  • ***
  • Posts: 150
Re: Record strangeness...
« Reply #11 on: June 12, 2024, 07:15:55 am »
Thanks ASerge,

Code: Pascal  [Select][+][-]
  1. MyRec.Create(...);

works indeed for my code.
I made a wrong assumption that one has to use

Code: Pascal  [Select][+][-]
  1. MyRec := TMyRec.Create(...);

similar as for classes. I wasn't aware of this.

Thaddy

  • Hero Member
  • *****
  • Posts: 16168
  • Censorship about opinions does not belong here.
Re: Record strangeness...
« Reply #12 on: June 12, 2024, 07:24:54 am »
Thanks Warfley, that makes sense. So I can't use the constructor in this case.
Yes, you can.
The original code would not update current, it would stay the same, but with a little known trick the code will work as expected:
records can contain const and const can be made writable. If you use it like this, current will always be the last record created instead of the first:
Code: Pascal  [Select][+][-]
  1. program crashmenot;
  2. {$mode delphi}{$apptype console}{$J+}
  3. type
  4.  
  5. PMyRec = ^TMyRec;
  6. TMyRec = record
  7.   const
  8.     Enclosing: PMyRec = nil; //!! writable const.
  9.   public
  10.     Name: String;
  11.     constructor Create(const aName: String);
  12. end;
  13.  
  14. var
  15.   Current: PMyRec; // gets a value later on
  16.  
  17.  
  18. constructor TMyRec.Create(const aName: String);
  19. begin
  20.   Name := aName;
  21.   Current := @Self;
  22.   Enclosing := Current;
  23.   writeln(self.name);
  24. end;
  25.  
  26. var
  27.  MyRec1,MyRec2,MyRec3:TMyrec;
  28.  begin
  29.   MyRec1 := TMyRec.Create('ABC');
  30.   MyRec2 := TMyRec.Create('DEF');
  31.   MyRec3 := TMyRec.Create('GHI');
  32.   Writeln(MyRec1.Name);
  33.   Writeln(MyRec2.Name);
  34.   Writeln(MyRec3.Name);
  35.   writeln(current^.name);
  36.   Readln;
  37. end.

For this to work across the program, you would need to implement  the copy operator to update current correctly.
That is, unless you assign like this:
Code: Pascal  [Select][+][-]
  1. current := @MyRec2;
« Last Edit: June 12, 2024, 07:53:15 am by Thaddy »
If I smell bad code it usually is bad code and that includes my own code.

 

TinyPortal © 2005-2018