渐渐地,我一直在使用更多变体 - 它们在某些地方非常有用,可以承载编译时未知的数据类型。一个有用的值是UnAssigned('我没有给你一个值')。我想我很久以前就发现了这个功能:
function DoSomething : variant;
begin
If SomeBoolean then
Result := 4.5
end;
似乎相当于:
function DoSomething : variant;
begin
If SomeBoolean then
Result := 4.5
else
Result := Unassigned; // <<<<
end;
我推测这种推理必须动态创建变体,如果SomeBoolean为FALSE,编译器已经创建了它,但它是'Unassigned'(&lt;&gt; nil?)。为了进一步鼓励这种思考,如果省略分配结果,编译器不会报告任何警告。
刚才我发现了令人讨厌的错误,我的第一个例子(其中'结果'未明确默认为'nil')实际上从其他地方返回了一个“旧”值。
在退回变体时,我是否应该总是分配结果(就像我在使用预定义类型时那样)?
答案 0 :(得分:9)
是的,您始终需要初始化函数的Result
,即使它是托管类型(例如string
和Variant
)。编译器会生成一些代码来为您初始化Variant
函数的未来返回值(至少我用于测试目的的Delphi 2010编译器),但编译器不保证您的Result已初始化;这只会使测试变得更加困难,因为您可能会遇到初始化Result的情况,根据该决策做出决定,以后发现您的代码有问题,因为在某些情况下Result 不是初始化。
从我的调查中,我注意到了:
首先,这是证明返回Variant
的函数收到var Result:
Variant
隐藏参数的证明。以下两个编译为完全相同的汇编程序,如下所示:
procedure RetVarProc(var V:Variant);
begin
V := 1;
end;
function RetVarFunc: Variant;
begin
Result := 1;
end;
// Generated assembler:
push ebx // needs to be saved
mov ebx, eax // EAX contains the address of the return Variant, copies that to EBX
mov eax, ebx // ... not a very smart compiler
mov edx, $00000001
mov cl, $01
call @VarFromInt
pop ebx
ret
接下来,有趣的是看看编译器如何设置对这两者的调用。以下是对具有var X:Variant
参数的过程的调用所发生的情况:
procedure Test;
var X: Variant;
begin
ProcThatTakesOneVarParameter(X);
end;
// compiles to:
lea eax, [ebp - $10]; // EAX gets the address of the local variable X
call ProcThatTakesOneVarParameter
如果我们将“X”设为全局变量,并且我们调用该函数返回Variant,我们得到以下代码:
var X: Variant;
procedure Test;
begin
X := FuncReturningVar;
end;
// compiles to:
lea eax, [ebp-$10] // EAX gets the address of a HIDDEN local variable.
call FuncReturningVar // Calls our function with the local variable as parameter
lea edx, [ebp-$10] // EDX gets the address of the same HIDDEN local variable.
mov eax, $00123445 // EAX is loaded with the address of the global variable X
call @VarCopy // This moves the result of FuncReturningVar into the global variable X
如果查看此函数的序言,您会注意到用作FuncReturningVar
调用的临时参数的局部变量被初始化为ZERO。如果该函数不包含任何Result :=
语句,则X
将为“未初始化”。如果我们再次调用该函数,则使用一个不同的临时和隐藏变量!以下是一些示例代码:
var X: Variant; // global variable
procedure Test;
begin
X := FuncReturningVar;
WriteLn(X); // Make sure we use "X"
X := FuncReturningVar;
WriteLn(X); // Again, make sure we use "X"
end;
// compiles to:
lea eax, [ebp-$10] // first local temporary
call FuncReturningVar
lea edx, [ebp-$10]
mov eax, $00123456
call @VarCopy
// [call to WriteLn using the actual address of X removed]
lea eax, [ebp-$20] // a DIFFERENT local temporary, again, initialized to Unassigned
call FuncReturningVar
// [ same as before, removed for brevity ]
在查看该代码时,您认为返回Variant的函数的“Result”是 allways 初始化为主叫方未分配。不对。如果在上一个测试中我们将“X”变量设为LOCAL变量(不是全局变量),则编译器不再使用两个单独的本地临时变量。所以我们有两个独立的情况,编译器生成不同的代码。换句话说,不要做任何假设,总是指定Result
。
我对不同行为的猜测:如果Variant
变量可以在当前作用域之外访问,作为全局变量(或者那个类的字段),编译器会生成使用线程安全的代码@VCCopy功能。如果变量是函数的本地变量,则没有多线程问题,因此编译器可以自由地进行直接赋值(不再调用@VarCopy)。
答案 1 :(得分:4)
我是否应该总是分配结果(就像我一样) 当使用预定义类型时) 撤回一个变种?
是。
测试一下:
function DoSomething(SomeBoolean: Boolean) : variant;
begin
if SomeBoolean then
Result := 1
end;
使用这样的功能:
var
xx: Variant;
begin
xx := DoSomething(True);
if xx <> Unassigned then
ShowMessage('Assigned');
xx := DoSomething(False);
if xx <> Unassigned then
ShowMessage('Assigned');
end;
在第二次调用DoSomething之后仍会分配xx。
将功能更改为:
function DoSomething(SomeBoolean: Boolean) : variant;
begin
Result := Unassigned;
if SomeBoolean then
Result := 1
end;
第二次调用DoSomething后没有分配xx。