2016-03-07 64 views
1

示例代碼的結果奇怪的行爲:有記錄的功能

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. 

此代碼給這個調試輸出:

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) 

看來,編譯器爲不同的變量建立不同代碼:

  • LSomeRec他們通過作爲var參數(作爲expecte d)。
  • 對於GSomeRec和FSomeRec編譯器,創建臨時變量,在爲正常變量賦值後傳遞她。

這是正常的嗎?如果是正常的,請給我鏈接到規範(文檔)。

PS

添加少量...

Here上寫着:

對於靜態數組,記錄,並設置結果,如果值佔用一個字節 它在AL中返回;如果該值佔用兩個字節,則在AX中返回 ;如果該值佔用4個字節,則返回EAX中的 。否則,結果被傳遞給函數的聲明參數

但事實上,這條規則是不成立後,一個額外的變量參數 返回。如果它包含調試器輸出如下:

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) 
+1

我不認爲這是重複的 - 返回值被初始化是答案的一部分,但我認爲OP期望隱式類操作符的行爲與它的行爲不同。具體來說,他們似乎預計'result'會預先加載左側變量的記錄內容(即:操作員將提供零碎分配/替換的目標記錄)。這根本不是那麼回事。 –

+0

我沒有把它寫進去,而且我也沒有看到作爲一個笨蛋關閉它有什麼不好。我們積極鼓勵這樣做。每個人都可以建立這些鏈接。我看不出有什麼比返回值沒有被初始化更多的東西。 –

+0

@Vasek您的重置方法需要麻煩。變異值的值類型的方法往往會造成混淆。傾向於使用簡單的常量賦值。 –

回答

5

最重要的一點是你的函數無法完全初始化返回值。 A function return value is not initialized,所以你不應該假設任何關於它的入門價值。

你是正確的觀察到,德爾福ABI執行大的返回值爲隱藏var參數。所以

function SomeFunc(Value: Integer): TSomeRec; 

轉化爲

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

然而,這並不意味着你可以做出的Result初始狀態的任何假設。當你寫:

Foo := SomeValue(42); 

你期待這轉化爲:

SomeValue(42, Foo); 

如果Foo是一個局部變量,然後這的確發生了什麼。否則,儘管使用了一個隱藏的臨時變量。代碼轉化爲:

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

這樣做的原因是編譯器無法保證非本地變量將是有效的。訪問非本地可能會導致訪問衝突。因此,編譯器的實現者決定使用臨時本地,以便如果發生訪問衝突,那麼它將在呼叫站點而不是在被調用者中被引發。

而且可能還有其他原因。

一個非常相關的問題,可能是重複的,可以在這裏找到:Is it necessary to assign a default value to a variant returned from a Delphi function?這個問題的一個關鍵區別,這一個是類型被認爲是有管理的,因此總是默認初始化,即使是局部變量(隱藏或以其他方式)。

但真正的一點是,這都是實施細節的問題。您需要了解function return values are not initialized,並且每個函數都必須初始化其返回值。

+0

關於初始化 - 忘了它)))關於ABI - 這是我需要的。在實際應用中,我有自己的顏色記錄。我希望用隱式運算符來編寫這個記錄,它們複製了沒有alpha通道的TColor類型。關於你的回答:我正確的理解,對於非本地變量編譯器總是會創建臨時變量,但不能保證。 – Vasek

+1

底線是您不能使用返回值將信息傳遞給函數。所以我確實相信這個問題都是關於初始化。返回值沒有被初始化。所以你必須完全初始化它們。 –