- 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'(<> nil?).为了进一步鼓励这种思考,如果省略分配结果,编译器不会报告任何警告.
刚才我发现了令人讨厌的错误,我的第一个例子(其中’结果’未明确默认为’nil’)实际上从其他地方返回了一个“旧”值.
在修复变体时,我是否应该总是分配结果(就像我在使用预定义类型时那样)?
解决方法
从我的调查中,我注意到:
>如果将结果分配给全局变量,则使用初始化的隐藏临时变量调用函数,从而产生结果被神奇初始化的错觉.
>如果对同一个全局变量进行两次赋值,则会得到两个不同的隐藏临时变量,重新强制初始化Result的错觉.
>如果对同一个全局变量进行两次赋值但在调用之间不使用全局变量,则编译器仅使用1个隐藏临时值,制动前一个规则!
>如果您的变量是调用过程的本地变量,则根本不使用中间隐藏的局部变量,因此不会初始化Result.
示范:
首先,这是证明返回Variant的函数接收var Result:
变量隐藏参数.以下两个编译为完全相同的汇编程序,如下所示:
- 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的函数的“结果”总是被调用方初始化为Unassigned.不对.如果在上一个测试中我们将“X”变量设为LOCAL变量(不是全局变量),则编译器不再使用两个单独的本地临时变量.所以我们有两个独立的情况,编译器生成不同的代码.换句话说,不做任何假设,总是指定结果.
我对不同行为的猜测:如果可以在当前作用域之外访问Variant变量,作为全局变量(或者该类的字段),编译器将生成使用线程安全的@VarCopy函数的代码.如果变量是函数的本地变量,则不存在多线程问题,因此编译器可以自由地进行直接赋值(不再调用@VarCopy).