delphi - 是否有必要为从 Delphi 函数返回的变量分配默认值?

标签 delphi function variant

渐渐地,我已经使用了更多的变体 - 它们在某些地方对于携带编译时未知的数据类型非常有用。一个有用的值是 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 (就像我在使用预定义类型时所做的那样)吗?

最佳答案

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

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

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

演示:

首先,这是返回 Variant 的函数接收 var 结果的证明: 变体隐藏参数。以下两个编译为完全相同的汇编程序,如下所示:

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 的临时参数的局部变量被初始化为 0。如果函数不包含任何 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 的函数的“结果”总是被调用方初始化为“未分配”。不对。如果在前面的测试中我们将“X”变量设置为 LOCAL 变量(不是全局变量),则编译器将不再使用这两个单独的局部临时变量。因此,我们有两种不同的情况,编译器生成不同的代码。换句话说,不要做任何假设,始终分配 Result

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

关于delphi - 是否有必要为从 Delphi 函数返回的变量分配默认值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6622340/

相关文章:

delphi - 如果我尝试使用 TRibbonCombobox 更改功能区样式,则访问冲突

c - 在不修改参数的情况下将指针传递给函数?

c++ - 优化 std::visit 可能吗?

c++ 变体没有这样的文件或目录

delphi - 如何在 Delphi 应用程序中嵌入除 IE<n> 之外的浏览器对象

delphi - 将 TAction 重命名为另一种形式

delphi - 全局范围的程序不能通用吗?此限制有技术原因吗?

javascript - 我如何在 javascript 中进行 var_dump?

javascript - JavaScript 参数语法不正确

c++ - 在 g++ 中使用 std::variant