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

发布于 2024-11-19 13:45:46 字数 627 浏览 2 评论 0原文

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

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

小伙你站住 2024-11-26 13:45:46

是的,您始终需要初始化函数的Result,即使它是托管类型(例如stringVariant)。编译器确实会生成一些代码来为您初始化 Variant 函数的未来返回值(至少我用于测试目的的 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)。

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).

我也只是我 2024-11-26 13:45:46

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

是的。

测试一下:

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。

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

Yes.

Test this:

function DoSomething(SomeBoolean: Boolean) : variant;
begin
    if SomeBoolean then
        Result := 1
end;

Use the function like this:

var
    xx: Variant;
begin
    xx := DoSomething(True);
    if xx <> Unassigned then
        ShowMessage('Assigned');

    xx := DoSomething(False);
    if xx <> Unassigned then
        ShowMessage('Assigned');
end;

xx will still be assigned after the second call to DoSomething.

Change the function to this:

function DoSomething(SomeBoolean: Boolean) : variant;
begin
    Result := Unassigned;
    if SomeBoolean then
        Result := 1
end;

And xx is not assigned after the second call to DoSomething.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文