2

Sample code:

unit Main;

interface

uses
  Winapi.Windows, System.SysUtils, Vcl.Forms;

type

  TSomeRec = record
    SomeData: Integer;
    SomePtr: Pointer;

    procedure Reset;
    class operator Implicit(const SomeData: Integer): TSomeRec;
  end;

  TMainForm = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FSomeRec: TSomeRec;
  end;

var
  MainForm: TMainForm;
  GSomeRec: TSomeRec;

implementation

{$R *.dfm}

function SomeFunc(Value: Integer): TSomeRec;
begin
  OutputDebugString(PWideChar(Result.SomeData.ToString + ' : ' + Integer(Result.SomePtr).ToString));
  Result.SomeData := Value;
end;

{ TSomeRec }

procedure TSomeRec.Reset;
begin
  SomeData := 5;
  SomePtr  := nil;
end;

class operator TSomeRec.Implicit(const SomeData: Integer): TSomeRec;
begin
  OutputDebugString(PWideChar(Result.SomeData.ToString + ' : ' + Integer(Result.SomePtr).ToString));
  Result.SomeData := SomeData;
end;

{ TMainForm }

procedure TMainForm.FormCreate(Sender: TObject);
var
  LSomeRec: TSomeRec;
begin
  LSomeRec.Reset;
  GSomeRec.Reset;
  FSomeRec.Reset;

  LSomeRec := 1;
  GSomeRec := 1;
  FSomeRec := 1;

  LSomeRec.Reset;
  GSomeRec.Reset;
  FSomeRec.Reset;

  LSomeRec := SomeFunc(1);
  GSomeRec := SomeFunc(1);
  FSomeRec := SomeFunc(1);
end;

end.

This code give this debug output:

Debug Output: 5 : 0 Process DPITest.exe (1764)
Debug Output: 172555996 : 1638080 Process DPITest.exe (1764)
Debug Output: 1 : 1638080 Process DPITest.exe (1764)
Debug Output: 5 : 0 Process DPITest.exe (1764)
Debug Output: 1 : 1638080 Process DPITest.exe (1764)
Debug Output: 1 : 1638080 Process DPITest.exe (1764)

It seems that the compiler for different variables create different code:

  • LSomeRec them pass as var parameter(as expected).
  • For GSomeRec and FSomeRec compiler create temporary variabale, passing her and after assign value for normal variable.

Is this normal? If is normal can you give me link to specification(documentation), please.

PS

A small addition...

Here it is written:

For static-array, record, and set results, if the value occupies one byte it is returned in AL; if the value occupies two bytes it is returned in AX; and if the value occupies four bytes it is returned in EAX. Otherwise, the result is returned in an additional var parameter that is passed to the function after the declared parameters

But in fact, this rule is not satisfied. If it holds debugger output would be as follows:

 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
 Debug Output: 5 : 0 Process DPITest.exe (1764)
Vasek
  • 454
  • 5
  • 13
  • 1
    I don't think this is a duplicate - return values being initialized is part of the answer, but I think OP is expecting the `implicit` class operator to behave differently than it does. Specifically, they seem to expect that `result` will come pre-loaded with the record contents of the left-side variable (ie: that operators will provide the destination record for piecemeal assignment/substitutions). This is simply not the case. – J... Mar 07 '16 at 18:03
  • I didn't read that into it, and I don't see what's bad about closing as a dupe. We are actively encouraged to do so. It is helpful to everyone to build these links. I can't see that there's anything more to this than return values not being initialized. – David Heffernan Mar 07 '16 at 18:16
  • @Vasek Your Reset method is asking for trouble. Methods of value types that mutate the value do tend to cause confusion. Prefer to use a simple assignment of a constant. – David Heffernan Mar 07 '16 at 18:21
  • 1
    @DavidHeffernan It looks like a fundamental misunderstanding about operator overloading to me. Here, when making an assignment like `GSomeRec := 1`, OP seems to expect that `result` in the function will be a reference to the variable on the left side of the operator (ie: that operator overloading provides opportunity to only partially modify the record being assigned to rather than requiring the operator to either assign to or completely initialize a new record for assignment). By luck this works for local variables (at least in this case), but the behaviour is undefined generally. – J... Mar 07 '16 at 18:21
  • There's nothing more than two functions that return an uninitialized value. – David Heffernan Mar 07 '16 at 18:33
  • @J... In code i have function "SomeFunc" which is also subject to this behavior. The actual operator works the same as a this function. So that the operator itself is not to blame for the problem. – Vasek Mar 07 '16 at 18:48
  • @DavidHeffernan 1) This question not about initialization. This question about what variable will be in result. 2) Reset method here for convenience only. – Vasek Mar 07 '16 at 18:48
  • @Vasek Yes, this question is 100% about the fact that the Result variable is not initialized. Did you read the other question and answers? – David Heffernan Mar 07 '16 at 18:50
  • @DavidHeffernan Yes, I have read other questions. And I could not find similar questions and answers that would respond to my question. I've updated the question, and I hope it has become more clear – Vasek Mar 07 '16 at 19:16
  • Perhaps against my better judgement I've reopened this so that I can explain this implementation detail in more detail. But I can't help wondering whether you are really trying to pass information in to the function through its return value. Or perhaps you are just trying to get your mental model of the ABI straight. – David Heffernan Mar 07 '16 at 19:30

1 Answers1

6

The most important point to make is that both of your functions fail to fully initialize the return value. A function return value is not initialized, and so you should not assume anything about its value on entry.

You are correct in observing that the Delphi ABI implements large return values as hidden var parameters. So

function SomeFunc(Value: Integer): TSomeRec;

is transformed into

procedure SomeFunc(Value: Integer; var Result: TSomeRec);

However, this does not mean that you can make any assumption about the initial state of Result. When you write:

Foo := SomeValue(42);

you are expecting this to be transformed into:

SomeValue(42, Foo);

If Foo is a local variable then this is indeed what happens. Otherwise though a hidden temporary variable is used. The code is transformed into:

var
  Temp: TSomeRec;
....
SomeValue(42, Temp);
Foo := Temp;

The reason for this is that the compiler cannot guarantee that a non-local variable will be valid. Accessing a non-local can lead to an access violation. And so the implementors of the compiler took the decision to use a temporary local so that if an access violation does occur then it will be raised at the call site and not in the callee.

And there may well have been other reasons for this.

A very related question, possibly a duplicate, can be found here: Is it necessary to assign a default value to a variant returned from a Delphi function? A key difference between that question and this one is that the type being considered there is managed and so always default initialized, even for a local variable (hidden or otherwise).

The real point though is that this is all a matter of implementation detail. You need to understand that function return values are not initialized, and every function must initialize its return values.

Community
  • 1
  • 1
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • About initialization - forget about it))) About ABI - here is what i need. In real application i have own color record. And I was hoping to write this record with implicit operators, which copies the type TColor without alpha channel. About you answer: I correct understand that for non-local variables compiler always create temporary variable, but it is not guaranteed. – Vasek Mar 07 '16 at 20:07
  • 1
    Bottom line is that you can't use the return value to pass information to a function. So I do believe this issue is all about initialization. Return values are not initialized. So you must completely initialize them. – David Heffernan Mar 07 '16 at 20:10