是否需要为从Delphi函数返回的变体赋一个默认值? [英] Is it necessary to assign a default value to a variant returned from a Delphi function?

查看:128
本文介绍了是否需要为从Delphi函数返回的变体赋一个默认值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

逐渐地,我一直在使用更多的变体 - 它们在某些地方非常有用,用于承载在编译时不知道的数据类型。一个有用的值是UnAssigned(我没有一个值)。我想我以前发现这个功能:

  function DoSomething:variant; 
begin
如果SomeBoolean然后
结果:= 4.5
end;

似乎相当于:

  function DoSomething:variant; 
begin
如果SomeBoolean然后
结果:= 4.5
else
结果:=未分配; //<<<
结束

我推测这个推理是必须动态创建一个变体,如果SomeBoolean为FALSE,编译器创建它,但它是未分配(<> nil?)。为了进一步鼓励这个想法,如果你省略了结果,编译器就不会报告。



刚才我发现了第一个例子(Result实际上从其他地方返回了一个旧值。



如果我一直在分配结果(如我在使用预定义类型时)一个变体?

解决方案

是的,你总是需要初始化 Result 的功能,即使它是托管类型(如 string Variant )。编译器确实生成一些代码,为您(至少用于测试目的的至少是Delphi 2010编译器)初始化 Variant 函数的未来返回值,但编译器不会保证您的结果初始化;这只会使测试变得更加困难,因为您可能遇到初始化结果的情况,基于您的决定,只有以后发现您的代码是错误的,因为在某些情况下,结果不是初始化



从我的调查中,我注意到:




  • 如果结果被分配给一个全局变量,你的函数被调用一个初始化的隐藏的临时变量,创建了结果神奇初始化的错觉。

  • 如果你对同一个全局变量进行两个赋值,你会得到两个不同的隐藏的临时变量,重新执行Result初始化的错觉。

  • 如果对同一个全局变量进行两个赋值,但不使用全局变量在调用之间,编译器只使用1个隐藏的临时,制动先前的规则!

  • 如果你的变量是本地的调用过程,没有中间人隐藏本地va可以使用riable,所以结果不是初始化的。



演示:



首先,这里证明返回一个 Variant 的函数收到一个 var Result:
变式
隐藏参数。以下两个编译到完全相同的汇编器,如下所示:

  procedure RetVarProc(var V:Variant); 
begin
V:= 1;
结束

函数RetVarFunc:Variant;
begin
结果:= 1;
结束

//生成的汇编程序:
push ebx //需要保存
mov ebx,eax // EAX包含返回Variant的地址,将其复制到EBX
mov eax,ebx // ...不是一个非常聪明的编译器
mov edx,$ 00000001
mov cl,$ 01
call @VarFromInt
pop ebx
ret

接下来,看看两者的调用如何由编译器设置很有趣。以下是对具有 var X:Variant 参数的过程的调用发生的情况:

 程序测试; 
var X:Variant;
begin
ProcThatTakesOneVarParameter(X);
结束

//编译为:
lea eax,[ebp - $ 10]; // EAX获取本地变量的地址X
调用ProcThatTakesOneVarParameter

如果我们这样做X是一个全局变量,我们称函数返回一个Variant,我们得到这个代码:

  var X:Variant; 

程序测试;
begin
X:= FuncReturningVar;
结束

//编译为:
lea eax,[ebp- $ 10] // EAX获取HIDDEN局部变量的地址。
调用FuncReturningVar //调用本地变量的函数作为参数
lea edx,[ebp- $ 10] // EDX获取相同HIDDEN局部变量的地址。
mov eax,$ 00123445 // EAX加载全局变量的地址X
调用@VarCopy //将FuncReturningVar的结果移动到全局变量X

如果您查看此函数的序言,您将注意到用作调用$ $ c $的临时参数的局部变量c> FuncReturningVar 被初始化为零。如果函数不包含任何 Result:= 语句,则 X 将是未初始化。如果我们再次调用该函数,则使用不同的临时和隐藏变量!以下是一些示例代码:

  var X:Variant; // global variable 
procedure Test;
begin
X:= FuncReturningVar;
WriteLn(X); //确保我们使用X
X:= FuncReturningVar;
WriteLn(X); //再次,确保我们使用X
end;

//编译为:
lea eax,[ebp- $ 10] //第一个本地临时
调用FuncReturningVar
lea edx,[ebp- $ 10]
mov eax,$ 00123456
调用@VarCopy
// [调用WriteLn使用X的实际地址]
lea eax,[ebp- $ 20] //一个不同的本地临时,再次,初始化为Unassigned
调用FuncReturningVar
// [与以前一样,为了简洁而删除]

当查看该代码时,您会认为返回Variant的函数的Result是由主叫方初始化为Unassigned。不对。如果在以前的测试中,我们将X变量设置为LOCAL变量(不是全局变量),则编译器不再使用两个单独的本地临时变量。所以我们有两个不同的情况,编译器生成不同的代码。换句话说,不要做任何假设,总是分配结果



我猜测不同的行为:如果 Variant 变量可以在当前范围之外访问,则作为全局变量(或该类的字段)),编译器将生成使用线程安全@ VarCopy功能。如果变量是函数本地的,那么没有多线程问题,所以编译器可以自由地进行直接分配(不再调用@VarCopy)。


Gradually I've been using more variants - they can be very useful in certain places for carrying data types that are not known at compile time. One useful value is UnAssigned ('I've not got a value for you'). I think I discovered a long time ago that the function:

function DoSomething : variant;
begin
  If SomeBoolean then
    Result := 4.5
end;

appeared to be equivalent to:

function DoSomething : variant;
begin 
  If SomeBoolean then
    Result := 4.5
   else
   Result := Unassigned; // <<<<
end;

I presumed this reasoning that a variant has to be created dynamically and if SomeBoolean was FALSE, the compiler had created it but it was 'Unassigned' (<> nil?). To further encourage this thinking, the compiler reports no warning if you omit assigning Result.

Just now I've spotted nasty bug where my first example (where 'Result' is not explicity defaulted to 'nil') actually returned an 'old' value from somewhere else.

Should I ALWAYS assign Result (as I do when using prefefined types) when returing a variant?

解决方案

Yes, you always need to initialize the Result of a function, even if it's a managed type (like string and Variant). The compiler does generate some code to initialize the future return value of a Variant function for you (at least the Delphi 2010 compiler I used for testing purposes does) but the compiler doesn't guarantee your Result is initialized; This only makes testing more difficult, because you might run into a case where your Result was initialized, base your decisions on that, only to later discover your code is buggy because under certain circumstances the Result wasn't initialized.

From my investigation, I've noticed:

  • If your result is assigned to a global variable, your function is called with an initialized hidden temporary variable, creating the illusion that the Result is magically initialized.
  • If you make two assignments to the same global variable, you'll get two distinct hidden temporary variables, re-enforcing the illusion that Result's are initialized.
  • If you make two assignments to the same global variable but don't use the global variable between calls, the compiler only uses 1 hidden temporary, braking the previous rule!
  • If your variable is local to the calling procedure, no intermediary hidden local variable is used at all, so the Result isn't initialized.

Demonstration:

First, here's the proof that a function returning a Variant receives a var Result: Variant hidden parameter. The following two compile to the exact same assembler, shown below:

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

Next, it's interesting to see how the call for the two is set up by the complier. Here's what happens for a call to a procedure that has a var X:Variant parameter:

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

If we make that "X" a global variable, and we call the function returning a Variant, we get this code:

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

If you look at the prologue of this function you'll notice the local variable that's used as a temporary parameter for the call to FuncReturningVar is initialized to ZERO. If the function doesn't contain any Result := statements, X would be "Uninitialized". If we call the function again, a DIFFERENT temporary and hidden variable is used! Here's a bit of sample code to see that:

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 ]

When looking at that code, you'd think the "Result" of a function returning Variant is allways initialized to Unassigned by the calling party. Not true. If in the previous test we make the "X" variable a LOCAL variable (not global), the compiler no longer uses the two separate local temporary variables. So we've got two separate cases where the compiler generates different code. In other words, don't make any assumptions, always assign Result.

My guess about the different behavior: If the Variant variable can be accessed outside the current scope, as a global variable (or class field for that matter) would, the compiler generates code that uses the thread-safe @VarCopy function. If the variable is local to the function there are no multi-threading issues so the compiler can take the liberty to make direct assignments (no-longer calling @VarCopy).

这篇关于是否需要为从Delphi函数返回的变体赋一个默认值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆