【问题标题】:Is it necessary to assign a default value to a variant returned from a Delphi function?是否有必要为从 Delphi 函数返回的变量分配默认值?
【发布时间】:2023-03-13 02:51:01
【问题描述】:

我逐渐使用了更多的变体——它们在某些地方对于携带编译时未知的数据类型非常有用。一个有用的值是 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,编译器已经创建了它,但它是“未分配”( nil?)。为了进一步鼓励这种想法,如果您省略分配 Result,编译器不会报告任何警告。

刚才我发现了一个令人讨厌的错误,我的第一个示例(其中“结果”未明确默认为“nil”)实际上从其他地方返回了一个“旧”值。

在返回变体时,我是否应该始终分配 Result(就像我在使用预定义类型时所做的那样)?

【问题讨论】:

  • IIRC 正是字符串的情况。不知道变体。
  • 你说得对,Ulrich - 我一直默认字符串结果,所以我从未注意到编译器转移到动态分配的字符串后,它不再警告尚未分配的结果。
  • +1 以获得深入了解 Delphi 内部结构的机会。

标签: delphi function variant


【解决方案1】:

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

根据我的调查,我注意到:

  • 如果将结果分配给全局变量,则会使用已初始化的隐藏临时变量调用函数,从而产生 Result 被神奇地初始化的错觉。
  • 如果您对同一个全局变量进行两次赋值,您将获得两个不同的隐藏临时变量,从而强化 Result 已初始化的错觉。
  • 如果您对同一个全局变量进行两次赋值,但在调用之间不使用该全局变量,则编译器仅使用 1 个隐藏的临时变量,打破了之前的规则!
  • 如果您的变量是调用过程的本地变量,则根本不使用中间隐藏的局部变量,因此结果没有初始化。

演示:

首先,这是一个返回 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 的临时参数的局部变量被初始化为零。如果函数不包含任何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 变量(不是全局变量),编译器将不再使用两个单独的局部临时变量。所以我们有两种不同的情况,编译器生成不同的代码。换句话说,不要做任何假设,总是分配Result

我对不同行为的猜测:如果可以在当前范围之外访问 Variant 变量,就像全局变量(或类字段)那样,编译器会生成使用线程安全的代码 @VarCopy功能。如果变量是函数的本地变量,则不存在多线程问题,因此编译器可以随意进行直接赋值(不再调用@VarCopy)。

【讨论】:

  • 我们正在寻找的那些答案 +1
【解决方案2】:

我应该总是分配结果吗(就像我做的那样 使用预定义类型时) 返回一个变体?

是的。

测试一下:

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。

【讨论】:

  • 您的答案是正确的,但该示例没有显示问题,因为 Delphi 的编译器调用DoSomething 的方式很奇怪。 Here's a console app tested on Delphi 2010,写东西的“如果”被标记为这样。
  • 想通了,看看我的回答:如果xx: Variant 是本地变量或全局变量,Mikael 的代码示例的行为会有所不同!将xx 设为全局变量,您将获得已分配/未分配。将其设为局部变量,您将获得 Assigned/Assigned。
  • 字符串也可能发生完全相同的事情。另外,为了让你的意图绝对清楚,无论如何你都应该这样做。
猜你喜欢
  • 2015-10-13
  • 1970-01-01
  • 1970-01-01
  • 2010-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多